From d53d7efa572db727e2a972bf2a2fc063309cc7be Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Sat, 18 Oct 2025 16:44:38 +0800 Subject: [PATCH 01/26] feat #167: 1/ finish refactor for d-engine-client and d-engine-proto 2/ migrate test-utils into d-engine-test-utils, waiting for fix all compile errors --- Cargo.lock | 641 +--------- Cargo.toml | 157 +-- d-engine-client/Cargo.lock | 1096 +++++++++++++++++ d-engine-client/Cargo.toml | 21 + .../client => d-engine-client/src}/builder.rs | 0 d-engine-client/src/client_extensions.rs | 329 +++++ .../client => d-engine-client/src}/cluster.rs | 11 +- .../src}/cluster_test.rs | 29 +- {src/client => d-engine-client/src}/config.rs | 2 +- {src/client => d-engine-client/src}/error.rs | 302 +++-- {src/client => d-engine-client/src}/kv.rs | 20 +- .../client => d-engine-client/src}/kv_test.rs | 44 +- d-engine-client/src/lib.rs | 153 +++ {src/client => d-engine-client/src}/pool.rs | 13 +- .../src}/pool_test.rs | 27 +- d-engine-client/src/scoped_timer.rs | 23 + d-engine-client/src/utils.rs | 22 + d-engine-core/Cargo.toml | 6 + d-engine-core/src/lib.rs | 17 + d-engine-proto/Cargo.toml | 17 + build.rs => d-engine-proto/build.rs | 10 +- .../proto/client}/client_api.proto | 6 +- {proto => d-engine-proto/proto}/common.proto | 9 +- {proto => d-engine-proto/proto}/error.proto | 26 +- .../proto/server/cluster.proto | 10 +- .../proto/server/election.proto | 6 +- .../proto/server/replication.proto | 8 +- .../proto/server}/storage.proto | 16 +- .../src/generated/d_engine.client.rs | 20 +- .../src/generated/d_engine.common.error.rs | 0 .../src/generated/d_engine.common.rs | 30 + .../src/generated/d_engine.error.rs | 119 ++ .../src/generated/d_engine.server.cluster.rs | 30 +- .../src/generated/d_engine.server.election.rs | 11 +- .../generated/d_engine.server.replication.rs | 12 +- .../src/generated/d_engine.server.storage.rs | 37 +- d-engine-proto/src/lib.rs | 33 + d-engine-server/Cargo.toml | 7 + d-engine-server/src/main.rs | 3 + d-engine-test-utils/Cargo.toml | 22 + .../src}/common.rs | 10 +- .../src}/entry_builder.rs | 6 +- .../src}/integration/mod.rs | 8 +- .../src}/integration/snapshot.rs | 6 +- .../mod.rs => d-engine-test-utils/src/lib.rs | 0 .../src}/mock/mock_builder.rs | 4 +- .../src}/mock/mock_rpc.rs | 50 +- .../src}/mock/mock_rpc_service.rs | 28 +- .../src}/mock/mock_storage_engine.rs | 0 .../src}/mock/mod.rs | 2 +- .../src}/mock_type_config.rs | 0 src/client/mod.rs | 339 ----- src/proto/client_extensions.rs | 35 - src/proto/mod.rs | 34 - 54 files changed, 2424 insertions(+), 1443 deletions(-) create mode 100644 d-engine-client/Cargo.lock create mode 100644 d-engine-client/Cargo.toml rename {src/client => d-engine-client/src}/builder.rs (100%) create mode 100644 d-engine-client/src/client_extensions.rs rename {src/client => d-engine-client/src}/cluster.rs (87%) rename {src/client => d-engine-client/src}/cluster_test.rs (87%) rename {src/client => d-engine-client/src}/config.rs (98%) rename {src/client => d-engine-client/src}/error.rs (65%) rename {src/client => d-engine-client/src}/kv.rs (95%) rename {src/client => d-engine-client/src}/kv_test.rs (97%) create mode 100644 d-engine-client/src/lib.rs rename {src/client => d-engine-client/src}/pool.rs (94%) rename {src/client => d-engine-client/src}/pool_test.rs (91%) create mode 100644 d-engine-client/src/scoped_timer.rs create mode 100644 d-engine-client/src/utils.rs create mode 100644 d-engine-core/Cargo.toml create mode 100644 d-engine-core/src/lib.rs create mode 100644 d-engine-proto/Cargo.toml rename build.rs => d-engine-proto/build.rs (73%) rename {proto => d-engine-proto/proto/client}/client_api.proto (95%) rename {proto => d-engine-proto/proto}/common.proto (95%) rename {proto => d-engine-proto/proto}/error.proto (79%) rename proto/cluster_management.proto => d-engine-proto/proto/server/cluster.proto (91%) rename proto/raft_election.proto => d-engine-proto/proto/server/election.proto (86%) rename proto/raft_replication.proto => d-engine-proto/proto/server/replication.proto (84%) rename {proto => d-engine-proto/proto/server}/storage.proto (86%) rename src/generated/raft.client.rs => d-engine-proto/src/generated/d_engine.client.rs (96%) rename src/generated/raft.error.rs => d-engine-proto/src/generated/d_engine.common.error.rs (100%) rename src/generated/raft.common.rs => d-engine-proto/src/generated/d_engine.common.rs (86%) create mode 100644 d-engine-proto/src/generated/d_engine.error.rs rename src/generated/raft.cluster.rs => d-engine-proto/src/generated/d_engine.server.cluster.rs (96%) rename src/generated/raft.election.rs => d-engine-proto/src/generated/d_engine.server.election.rs (96%) rename src/generated/raft.replication.rs => d-engine-proto/src/generated/d_engine.server.replication.rs (96%) rename src/generated/raft.storage.rs => d-engine-proto/src/generated/d_engine.server.storage.rs (94%) create mode 100644 d-engine-proto/src/lib.rs create mode 100644 d-engine-server/Cargo.toml create mode 100644 d-engine-server/src/main.rs create mode 100644 d-engine-test-utils/Cargo.toml rename {src/test_utils => d-engine-test-utils/src}/common.rs (94%) rename {src/test_utils => d-engine-test-utils/src}/entry_builder.rs (89%) rename {src/test_utils => d-engine-test-utils/src}/integration/mod.rs (97%) rename {src/test_utils => d-engine-test-utils/src}/integration/snapshot.rs (98%) rename src/test_utils/mod.rs => d-engine-test-utils/src/lib.rs (100%) rename {src/test_utils => d-engine-test-utils/src}/mock/mock_builder.rs (99%) rename {src/test_utils => d-engine-test-utils/src}/mock/mock_rpc.rs (83%) rename {src/test_utils => d-engine-test-utils/src}/mock/mock_rpc_service.rs (92%) rename {src/test_utils => d-engine-test-utils/src}/mock/mock_storage_engine.rs (100%) rename {src/test_utils => d-engine-test-utils/src}/mock/mod.rs (98%) rename {src/test_utils => d-engine-test-utils/src}/mock_type_config.rs (100%) delete mode 100644 src/client/mod.rs delete mode 100644 src/proto/client_extensions.rs delete mode 100644 src/proto/mod.rs diff --git a/Cargo.lock b/Cargo.lock index d50550f0..a54e9524 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,18 +17,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -38,12 +26,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "anstyle" version = "1.0.11" @@ -64,15 +46,13 @@ checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "async-compression" -version = "0.4.29" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bee399cc3a623ec5a2db2c5b90ee0190a2260241fbe0c023ac8f7bab426aaf8" +checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" dependencies = [ "compression-codecs", "compression-core", - "flate2", "futures-core", - "memchr", "pin-project-lite", "tokio", ] @@ -229,21 +209,6 @@ version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - [[package]] name = "bytes" version = "1.10.1" @@ -289,12 +254,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "clang-sys" version = "1.8.1" @@ -308,15 +267,13 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.29" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7eea68f0e02c2b0aa8856e9a9478444206d4b6828728e7b0697c0f8cca265cb" +checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" dependencies = [ "compression-core", "flate2", - "futures-core", "memchr", - "pin-project-lite", ] [[package]] @@ -325,27 +282,6 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" -[[package]] -name = "config" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" -dependencies = [ - "nom", - "pathdiff", - "serde", - "toml", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - [[package]] name = "crc32fast" version = "1.5.0" @@ -356,165 +292,78 @@ dependencies = [ ] [[package]] -name = "crossbeam" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +name = "d-engine-client" +version = "0.2.0" dependencies = [ - "crossbeam-utils", + "arc-swap", + "bytes", + "d-engine-proto", + "d-engine-test-utils", + "futures", + "mockall", + "rand", + "serde", + "tokio", + "tonic", + "tracing", + "tracing-test", ] [[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] +name = "d-engine-core" +version = "0.1.0" [[package]] -name = "crossbeam-skiplist" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b" +name = "d-engine-proto" +version = "0.2.0" dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", + "bytes", + "prost", + "serde", + "tonic", + "tonic-build", + "tracing", + "vergen", ] [[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +name = "d-engine-server" +version = "0.1.0" dependencies = [ - "generic-array", - "typenum", + "rocksdb", ] [[package]] -name = "d-engine" -version = "0.1.4" +name = "d-engine-test-utils" +version = "0.2.0" dependencies = [ - "arc-swap", "async-compression", - "async-stream", "bincode", "bytes", - "config", "crc32fast", - "crossbeam", - "crossbeam-skiplist", - "dashmap", - "flate2", + "d-engine-proto", "futures", "http-body", "http-body-util", - "hyper", - "lru", - "memmap2", - "metrics", - "mockall", - "nanoid", - "nix", - "once_cell", - "parking_lot", "prost", - "rand", - "rcgen", - "ring", - "rocksdb", - "serde", - "serial_test", - "sha2", - "temp-env", "tempfile", - "thiserror", "tokio", "tokio-stream", "tokio-tar", - "tokio-util", "tonic", - "tonic-build", "tonic-health", "tracing", - "tracing-subscriber", - "tracing-test", - "uuid", - "vergen", -] - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", ] [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", ] -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - [[package]] name = "downcast" version = "0.11.0" @@ -574,7 +423,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", - "libz-sys", "miniz_oxide", ] @@ -584,12 +432,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "fragile" version = "2.0.1" @@ -685,16 +527,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getrandom" version = "0.2.16" @@ -755,22 +587,11 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] [[package]] name = "heck" @@ -946,16 +767,6 @@ dependencies = [ "libc", ] -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -980,13 +791,13 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.9.3", "libc", - "redox_syscall 0.5.17", + "redox_syscall 0.5.18", ] [[package]] @@ -1021,31 +832,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" -[[package]] -name = "lock_api" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" -[[package]] -name = "lru" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0281c2e25e62316a5c9d98f2d2e9e95a37841afdaf4383c177dbb5c1dfab0568" -dependencies = [ - "hashbrown 0.15.5", -] - [[package]] name = "lz4-sys" version = "1.11.1+lz4-1.10.0" @@ -1077,25 +869,6 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" -[[package]] -name = "memmap2" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" -dependencies = [ - "libc", -] - -[[package]] -name = "metrics" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dea7ac8057892855ec285c440160265225438c3c45072613c25a4b26e98ef5" -dependencies = [ - "ahash", - "portable-atomic", -] - [[package]] name = "mime" version = "0.3.17" @@ -1161,27 +934,6 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand", -] - -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.9.3", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "nom" version = "7.1.3" @@ -1231,45 +983,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "parking_lot" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.17", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "pem" -version = "3.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" -dependencies = [ - "base64", - "serde", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -1324,12 +1037,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "portable-atomic" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" - [[package]] name = "powerfmt" version = "0.2.0" @@ -1487,19 +1194,6 @@ dependencies = [ "getrandom 0.2.16", ] -[[package]] -name = "rcgen" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" -dependencies = [ - "pem", - "ring", - "rustls-pki-types", - "time", - "yasna", -] - [[package]] name = "redox_syscall" version = "0.3.5" @@ -1511,9 +1205,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.9.3", ] @@ -1646,27 +1340,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "scc" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" -dependencies = [ - "sdd", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sdd" -version = "3.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" - [[package]] name = "serde" version = "1.0.219" @@ -1687,51 +1360,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serial_test" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" -dependencies = [ - "futures", - "log", - "once_cell", - "parking_lot", - "scc", - "serial_test_derive", -] - -[[package]] -name = "serial_test_derive" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -1811,15 +1439,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -[[package]] -name = "temp-env" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96374855068f47402c3121c6eed88d29cb1de8f3ab27090e273e420bdabcf050" -dependencies = [ - "parking_lot", -] - [[package]] name = "tempfile" version = "3.21.0" @@ -1839,26 +1458,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "thread_local" version = "1.1.9" @@ -1870,9 +1469,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -1887,15 +1486,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -1980,47 +1579,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap 2.11.0", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "tonic" version = "0.12.3" @@ -2215,12 +1773,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - [[package]] name = "unicode-ident" version = "1.0.18" @@ -2233,17 +1785,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "uuid" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" -dependencies = [ - "getrandom 0.3.3", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "valuable" version = "0.1.1" @@ -2268,12 +1809,6 @@ dependencies = [ "time", ] -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "want" version = "0.3.1" @@ -2298,64 +1833,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - [[package]] name = "windows-link" version = "0.1.3" @@ -2518,15 +1995,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" -[[package]] -name = "winnow" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" -dependencies = [ - "memchr", -] - [[package]] name = "wit-bindgen" version = "0.45.0" @@ -2535,23 +2003,14 @@ checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" [[package]] name = "xattr" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", "rustix", ] -[[package]] -name = "yasna" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" -dependencies = [ - "time", -] - [[package]] name = "zerocopy" version = "0.8.26" diff --git a/Cargo.toml b/Cargo.toml index f0ec792b..90df195a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,29 +1,47 @@ -[package] -name = "d-engine" -version = "0.1.4" -edition = "2021" -resolver = "2" # Workspace-level unified configuration -authors = ["Joshua Chi "] -categories = ["concurrency", "asynchronous", "algorithms", "database"] -keywords = [ - "raft", - "consensus", - "distributed-systems", - "storage", - "fault-tolerance", +[workspace] +members = [ + "d-engine-proto", + "d-engine-core", + "d-engine-client", + "d-engine-server", + "d-engine-test-utils", ] -description = "A lightweight and strongly consistent Raft consensus engine written in Rust. It is a base to build reliable and scalable distributed systems." -homepage = "https://github.com/deventlab/d-engine" -license = "MIT OR Apache-2.0" -repository = "https://github.com/deventlab/d-engine" - -[features] -default = [] # Optional: Include "rocks" in default if desired -rocksdb = [ - "dep:rocksdb", -] # Enable rocksdb dependency when "rocks" feature is active - -[dependencies] + +resolver = "2" + +[workspace.dependencies] +d-engine-proto = { path = "./d-engine-proto" } +d-engine-core = { path = "./d-engine-core" } +d-engine-client = { path = "./d-engine-client" } +d-engine-server = { path = "./d-engine-server" } +d-engine-test-utils = { path = "./d-engine-test-utils" } + +# [package] +# name = "d-engine" +# version = "0.1.4" +# edition = "2021" +# resolver = "2" # Workspace-level unified configuration +# authors = ["Joshua Chi "] +# categories = ["concurrency", "asynchronous", "algorithms", "database"] +# keywords = [ +# "raft", +# "consensus", +# "distributed-systems", +# "storage", +# "fault-tolerance", +# ] +# description = "A lightweight and strongly consistent Raft consensus engine written in Rust. It is a base to build reliable and scalable distributed systems." +# homepage = "https://github.com/deventlab/d-engine" +# license = "MIT OR Apache-2.0" +# repository = "https://github.com/deventlab/d-engine" + +# [features] +# default = [] # Optional: Include "rocks" in default if desired +# rocksdb = [ +# "dep:rocksdb", +# ] # Enable rocksdb dependency when "rocks" feature is active + +# [dependencies] serde = { version = "1.0", default-features = false, features = ["derive"] } prost = { version = "0.13", default-features = false } bincode = "1.3" @@ -46,7 +64,7 @@ tokio-stream = "0.1.16" tokio-util = "0.7.11" futures = "0.3.30" -rocksdb = { version = "0.24.0", optional = true } + # Monitoring metrics = { version = "0.24", features = [] } @@ -59,7 +77,6 @@ config = { version = "0.14.0", default-features = false, features = ["toml"] } nanoid = "0.4.0" # using Dashset dashmap = "5.5.3" -arc-swap = "1.7.1" bytes = { version = "1.10", features = ["serde"] } tempfile = "3.19.1" crc32fast = "1.4.2" @@ -83,46 +100,46 @@ ring = { version = "=0.17.14", features = ["std"] } crossbeam-skiplist = "0.1" crossbeam = "0.8" -[dev-dependencies] -mockall = "0.12.1" -tracing-subscriber = "0.3.20" -once_cell = "1.20.2" -tokio = { version = "1", features = ["test-util", "process"] } -serial_test = "3.2.0" -hyper = "1.6.0" -temp-env = "0.3.6" -nix = { version = "0.30.1", features = ["fs"] } -tracing-test = "0.2" -uuid = { version = "1", features = ["v4"] } - -[build-dependencies] -tonic-build = "0.12" -vergen = { version = "8.1", features = ["git", "gitcl"] } - -[profile.dev] -opt-level = 1 # Main code is low-optimized to speed up compilation -incremental = true # Enable incremental compilation (enabled by default, requires confirmation) -codegen-units = 16 # Appropriately reduce parallel units to reduce memory usage -lto = false - -[profile.dev.package."*"] -opt-level = 3 # All dependency optimization levels - -[profile.release] -incremental = true -debug = false -# opt-level = 3 -opt-level = 'z' # Optimized for minimum size -overflow-checks = true -lto = true # Eliminate redundant code and reduce size -codegen-units = 1 # Reduce parallel code generation units to improve optimization effect - - -[profile.test] -incremental = true -codegen-units = 16 # Take advantage of M2 multi-core -split-debuginfo = "unpacked" # macOS-specific optimization -debug = 1 # Reduce debug information - -[profile.test.build-override] -opt-level = 3 # Full optimization of dependencies +# [dev-dependencies] +# mockall = "0.12.1" +# tracing-subscriber = "0.3.20" +# once_cell = "1.20.2" +# tokio = { version = "1", features = ["test-util", "process"] } +# serial_test = "3.2.0" +# hyper = "1.6.0" +# temp-env = "0.3.6" +# nix = { version = "0.30.1", features = ["fs"] } +# tracing-test = "0.2" +# uuid = { version = "1", features = ["v4"] } + +# [build-dependencies] +# tonic-build = "0.12" +# vergen = { version = "8.1", features = ["git", "gitcl"] } + +# [profile.dev] +# opt-level = 1 # Main code is low-optimized to speed up compilation +# incremental = true # Enable incremental compilation (enabled by default, requires confirmation) +# codegen-units = 16 # Appropriately reduce parallel units to reduce memory usage +# lto = false + +# [profile.dev.package."*"] +# opt-level = 3 # All dependency optimization levels + +# [profile.release] +# incremental = true +# debug = false +# # opt-level = 3 +# opt-level = 'z' # Optimized for minimum size +# overflow-checks = true +# lto = true # Eliminate redundant code and reduce size +# codegen-units = 1 # Reduce parallel code generation units to improve optimization effect + + +# [profile.test] +# incremental = true +# codegen-units = 16 # Take advantage of M2 multi-core +# split-debuginfo = "unpacked" # macOS-specific optimization +# debug = 1 # Reduce debug information + +# [profile.test.build-override] +# opt-level = 3 # Full optimization of dependencies diff --git a/d-engine-client/Cargo.lock b/d-engine-client/Cargo.lock new file mode 100644 index 00000000..cb5e7710 --- /dev/null +++ b/d-engine-client/Cargo.lock @@ -0,0 +1,1096 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "d-engine-client" +version = "0.1.0" +dependencies = [ + "bytes", + "tonic", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "flate2" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.12.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2 0.6.1", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2 0.6.1", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "flate2", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "rustls-pemfile", + "socket2 0.5.10", + "tokio", + "tokio-rustls", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" diff --git a/d-engine-client/Cargo.toml b/d-engine-client/Cargo.toml new file mode 100644 index 00000000..87d0b8b7 --- /dev/null +++ b/d-engine-client/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "d-engine-client" +version = "0.2.0" +edition = "2024" + +[dependencies] +d-engine-proto = { workspace = true } + +tokio = { workspace = true } +tonic = { workspace = true } +bytes = { workspace = true } +tracing = { workspace = true } +serde = { workspace = true } +rand = { workspace = true } +arc-swap = "1.7.1" +futures = { workspace = true } + +[dev-dependencies] +d-engine-test-utils = { workspace = true } +mockall = "0.12.1" +tracing-test = "0.2" diff --git a/src/client/builder.rs b/d-engine-client/src/builder.rs similarity index 100% rename from src/client/builder.rs rename to d-engine-client/src/builder.rs diff --git a/d-engine-client/src/client_extensions.rs b/d-engine-client/src/client_extensions.rs new file mode 100644 index 00000000..251dca03 --- /dev/null +++ b/d-engine-client/src/client_extensions.rs @@ -0,0 +1,329 @@ +//! Extensions for protobuf-generated client types +//! +//! This module provides additional methods for protobuf-generated types +//! that are not automatically generated by the protobuf compiler. + +use bytes::Bytes; +use d_engine_proto::client::ClientReadRequest; +use d_engine_proto::client::ClientResult; +use d_engine_proto::client::client_response::SuccessResult; +use d_engine_proto::client::write_command; +use tracing::error; + +use crate::ClientApiError; +use d_engine_proto::client::ClientResponse; +use d_engine_proto::client::ReadConsistencyPolicy; +use d_engine_proto::client::ReadResults; +use d_engine_proto::client::WriteCommand; +use d_engine_proto::error::ErrorCode; + +pub trait ClientReadRequestExt { + /// Checks if the consistency_policy field is present in the request + /// + /// Returns true if the client explicitly specified a consistency policy, + /// false if the field is absent (should use server default). + fn has_consistency_policy(&self) -> bool; + + /// Gets the consistency policy value safely + /// + /// Returns Some(policy) if present, None if field is absent. + /// Safer alternative that doesn't panic. + fn get_consistency_policy(&self) -> Option; +} + +/// Extension trait for ClientResponse providing utility methods +/// +/// This trait adds client-side functionality to the protobuf-generated +/// ClientResponse type, making it easier to work with responses. +pub trait ClientResponseExt { + /// Build success response for write operations + /// + /// # Returns + /// Response with NoError code and write confirmation + fn write_success() -> ClientResponse; + + /// Check if the write operation was successful + /// + /// # Returns + /// - `true` if the response indicates a successful write operation + /// - `false` if the response indicates a failed write operation or is not a write response + fn is_write_success(&self) -> bool; + + /// Build success response for read operations + /// + /// # Parameters + /// - `results`: Vector of retrieved key-value pairs + fn read_results(results: Vec) -> Self; + + /// Build generic error response for any operation type + /// + /// # Parameters + /// - `error_code`: Predefined client request error code + fn client_error(error_code: ErrorCode) -> Self; + + /// Convert response to boolean write result + /// + /// # Returns + /// - `Ok(true)` on successful write + /// - `Err` with converted error code on failure + fn into_write_result(&self) -> std::result::Result; + + /// Convert response to read results + /// + /// # Returns + /// Vector of optional key-value pairs wrapped in Result + fn into_read_results(&self) -> std::result::Result>, ClientApiError>; + + /// Validate error code in response header + /// + /// # Internal Logic + /// Converts numeric error code to enum variant + fn validate_error(&self) -> std::result::Result<(), ClientApiError>; + + /// Check if this response indicates the leader's term is outdated + fn is_term_outdated(&self) -> bool; + + /// Check if this response indicates a quorum timeout or failure to receive majority responses + fn is_quorum_timeout_or_failure(&self) -> bool; + + /// Check if this response indicates a failure to receive majority responses + fn is_propose_failure(&self) -> bool; + + /// Check if this response indicates a a retry required + fn is_retry_required(&self) -> bool; +} + +pub trait WriteCommandExt { + /// Create write command for key-value pair + /// + /// # Parameters + /// - `key`: Byte array for storage key + /// - `value`: Byte array to be stored + fn insert( + key: impl Into, + value: impl Into, + ) -> Self; + + /// Create deletion command for specified key + /// + /// # Parameters + /// - `key`: Byte array of key to delete + fn delete(key: impl Into) -> Self; +} + +pub trait ErrorCodeExt { + /// Check if this error indicates the leader's term is outdated + fn is_term_outdated(&self) -> bool; + + /// Check if this error indicates a quorum timeout or failure to receive majority responses + fn is_quorum_timeout_or_failure(&self) -> bool; + + /// Check if this error indicates a failure to receive majority responses + fn is_propose_failure(&self) -> bool; + + /// Check if this error indicates a retry required + fn is_retry_required(&self) -> bool; +} + +impl ClientReadRequestExt for ClientReadRequest { + /// Checks if the consistency_policy field is present in the request + /// + /// Returns true if the client explicitly specified a consistency policy, + /// false if the field is absent (should use server default). + fn has_consistency_policy(&self) -> bool { + self.consistency_policy.is_some() + } + + /// Gets the consistency policy value safely + /// + /// Returns Some(policy) if present, None if field is absent. + /// Safer alternative that doesn't panic. + fn get_consistency_policy(&self) -> Option { + self.consistency_policy.and_then(|policy_i32| { + match policy_i32 { + x if x == ReadConsistencyPolicy::LeaseRead as i32 => { + Some(ReadConsistencyPolicy::LeaseRead) + } + x if x == ReadConsistencyPolicy::LinearizableRead as i32 => { + Some(ReadConsistencyPolicy::LinearizableRead) + } + _ => None, // Invalid value + } + }) + } +} + +impl WriteCommandExt for WriteCommand { + /// Create write command for key-value pair + /// + /// # Parameters + /// - `key`: Byte array for storage key + /// - `value`: Byte array to be stored + fn insert( + key: impl Into, + value: impl Into, + ) -> Self { + let cmd = write_command::Insert { + key: key.into(), + value: value.into(), + }; + Self { + operation: Some(write_command::Operation::Insert(cmd)), + } + } + + /// Create deletion command for specified key + /// + /// # Parameters + /// - `key`: Byte array of key to delete + fn delete(key: impl Into) -> Self { + let cmd = write_command::Delete { key: key.into() }; + Self { + operation: Some(write_command::Operation::Delete(cmd)), + } + } +} + +impl ClientResponseExt for ClientResponse { + /// Build success response for write operations + /// + /// # Returns + /// Response with NoError code and write confirmation + fn write_success() -> Self { + Self { + error: ErrorCode::Success as i32, + success_result: Some(SuccessResult::WriteAck(true)), + metadata: None, + } + } + + /// Check if the write operation was successful + /// + /// # Returns + /// - `true` if the response indicates a successful write operation + /// - `false` if the response indicates a failed write operation or is not a write response + fn is_write_success(&self) -> bool { + self.error == ErrorCode::Success as i32 + && matches!(self.success_result, Some(SuccessResult::WriteAck(true))) + } + + /// Build success response for read operations + /// + /// # Parameters + /// - `results`: Vector of retrieved key-value pairs + fn read_results(results: Vec) -> Self { + Self { + error: ErrorCode::Success as i32, + success_result: Some(SuccessResult::ReadData(ReadResults { results })), + metadata: None, + } + } + + /// Build generic error response for any operation type + /// + /// # Parameters + /// - `error_code`: Predefined client request error code + fn client_error(error_code: ErrorCode) -> Self { + Self { + error: error_code as i32, + success_result: None, + metadata: None, + } + } + + /// Convert response to boolean write result + /// + /// # Returns + /// - `Ok(true)` on successful write + /// - `Err` with converted error code on failure + fn into_write_result(&self) -> std::result::Result { + self.validate_error()?; + Ok(match self.success_result { + Some(SuccessResult::WriteAck(success)) => success, + _ => false, + }) + } + + /// Convert response to read results + /// + /// # Returns + /// Vector of optional key-value pairs wrapped in Result + fn into_read_results(&self) -> std::result::Result>, ClientApiError> { + self.validate_error()?; + match &self.success_result { + Some(SuccessResult::ReadData(data)) => data + .results + .clone() + .into_iter() + .map(|item| { + Ok(Some(ClientResult { + key: item.key, + value: item.value, + })) + }) + .collect(), + _ => { + error!("Invalid response type for read operation"); + unreachable!() + } + } + } + + /// Validate error code in response header + /// + /// # Internal Logic + /// Converts numeric error code to enum variant + fn validate_error(&self) -> std::result::Result<(), ClientApiError> { + match ErrorCode::try_from(self.error).unwrap_or(ErrorCode::Uncategorized) { + ErrorCode::Success => Ok(()), + e => Err(e.into()), + } + } + + /// Check if this response indicates the leader's term is outdated + fn is_term_outdated(&self) -> bool { + ErrorCode::try_from(self.error).map(|e| e.is_term_outdated()).unwrap_or(false) + } + + /// Check if this response indicates a quorum timeout or failure to receive majority responses + fn is_quorum_timeout_or_failure(&self) -> bool { + ErrorCode::try_from(self.error) + .map(|e| e.is_quorum_timeout_or_failure()) + .unwrap_or(false) + } + + /// Check if this response indicates a failure to receive majority responses + fn is_propose_failure(&self) -> bool { + ErrorCode::try_from(self.error).map(|e| e.is_propose_failure()).unwrap_or(false) + } + + /// Check if this response indicates a a retry required + fn is_retry_required(&self) -> bool { + ErrorCode::try_from(self.error).map(|e| e.is_retry_required()).unwrap_or(false) + } +} + +impl ErrorCodeExt for ErrorCode { + /// Check if this error indicates the leader's term is outdated + fn is_term_outdated(&self) -> bool { + matches!(self, ErrorCode::TermOutdated) + } + + /// Check if this error indicates a quorum timeout or failure to receive majority responses + fn is_quorum_timeout_or_failure(&self) -> bool { + matches!( + self, + ErrorCode::ConnectionTimeout | ErrorCode::ProposeFailed | ErrorCode::ClusterUnavailable + ) + } + + /// Check if this error indicates a failure to receive majority responses + fn is_propose_failure(&self) -> bool { + matches!(self, ErrorCode::ProposeFailed) + } + + /// Check if this error indicates a retry required + fn is_retry_required(&self) -> bool { + matches!(self, ErrorCode::RetryRequired) + } +} diff --git a/src/client/cluster.rs b/d-engine-client/src/cluster.rs similarity index 87% rename from src/client/cluster.rs rename to d-engine-client/src/cluster.rs index 547ecdc9..21047533 100644 --- a/src/client/cluster.rs +++ b/d-engine-client/src/cluster.rs @@ -8,12 +8,11 @@ use tracing::error; use tracing::instrument; use super::ClientInner; -use crate::proto::cluster::cluster_management_service_client::ClusterManagementServiceClient; -use crate::proto::cluster::JoinRequest; -use crate::proto::cluster::JoinResponse; -use crate::proto::cluster::NodeMeta; use crate::ClientApiError; -use crate::Result; +use d_engine_proto::server::cluster::JoinRequest; +use d_engine_proto::server::cluster::JoinResponse; +use d_engine_proto::server::cluster::NodeMeta; +use d_engine_proto::server::cluster::cluster_management_service_client::ClusterManagementServiceClient; /// Cluster administration interface /// @@ -44,7 +43,7 @@ impl ClusterClient { /// - IP address /// - Port /// - Role (Leader/Follower) - pub async fn list_members(&self) -> Result> { + pub async fn list_members(&self) -> std::result::Result, ClientApiError> { let client_inner = self.client_inner.load(); Ok(client_inner.pool.get_all_members()) diff --git a/src/client/cluster_test.rs b/d-engine-client/src/cluster_test.rs similarity index 87% rename from src/client/cluster_test.rs rename to d-engine-client/src/cluster_test.rs index 203cd9ea..7634e812 100644 --- a/src/client/cluster_test.rs +++ b/d-engine-client/src/cluster_test.rs @@ -2,22 +2,21 @@ use std::sync::Arc; use std::vec; use arc_swap::ArcSwap; +use d_engine_proto::common::NodeRole; use tokio::sync::oneshot; use tonic::Status; use tracing_test::traced_test; -use crate::proto::cluster::ClusterMembership; -use crate::proto::cluster::JoinResponse; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::NodeStatus; -use crate::proto::error::ErrorCode; -use crate::test_utils::MockNode; use crate::ClientConfig; use crate::ClientInner; use crate::ClusterClient; use crate::ConnectionPool; -use crate::FOLLOWER; -use crate::LEADER; +use crate::test_utils::MockNode; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::error::ErrorCode; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::JoinResponse; +use d_engine_proto::server::cluster::NodeMeta; #[tokio::test] #[traced_test] @@ -48,7 +47,7 @@ async fn test_list_members_success() { let members = client.list_members().await.expect("Should get members"); assert_eq!(members.len(), 1); - assert_eq!(members[0].role, LEADER); + assert_eq!(members[0].role, NodeRole::Leader.into()); } #[tokio::test] @@ -62,7 +61,7 @@ async fn test_join_cluster_success() { version: 1, nodes: vec![NodeMeta { id: 1, - role: LEADER, + role: NodeRole::Leader.into(), address: format!("127.0.0.1:{port}",), status: NodeStatus::Active.into(), }], @@ -92,7 +91,7 @@ async fn test_join_cluster_success() { let request = NodeMeta { id: 2, - role: FOLLOWER, + role: NodeRole::Follower.into(), address: "127.0.0.1:50052".to_string(), status: NodeStatus::Active.into(), }; @@ -131,7 +130,7 @@ async fn test_join_cluster_failure() { let request = NodeMeta { id: 2, - role: LEADER, + role: NodeRole::Leader.into(), address: "127.0.0.1:50052".to_string(), status: NodeStatus::Active.into(), }; @@ -139,13 +138,13 @@ async fn test_join_cluster_failure() { // Simulate leader rejection let mut leader_node = NodeMeta { id: 1, - role: LEADER, + role: NodeRole::Leader.into(), address: "127.0.0.1:50051".to_string(), status: NodeStatus::Active.into(), }; - leader_node.role = LEADER; + leader_node.role = NodeRole::Leader.into(); let result = client.join_cluster(request).await; assert!(result.is_err()); - assert_eq!(result.unwrap_err().code(), ErrorCode::StaleOperation as u32); + assert_eq!(result.unwrap_err().code(), ErrorCode::StaleOperation); } diff --git a/src/client/config.rs b/d-engine-client/src/config.rs similarity index 98% rename from src/client/config.rs rename to d-engine-client/src/config.rs index 12d5f266..cd33f7bc 100644 --- a/src/client/config.rs +++ b/d-engine-client/src/config.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::time::get_now_as_u32; +use crate::utils::get_now_as_u32; /// Client configuration parameters for network connection management /// diff --git a/src/client/error.rs b/d-engine-client/src/error.rs similarity index 65% rename from src/client/error.rs rename to d-engine-client/src/error.rs index 2502ec28..3dec6e9a 100644 --- a/src/client/error.rs +++ b/d-engine-client/src/error.rs @@ -6,15 +6,14 @@ use tokio::task::JoinError; use tonic::Code; use tonic::Status; -use crate::proto::error::ErrorCode; +use d_engine_proto::error::ErrorCode; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ClientApiError { /// Network layer error (retryable) #[serde(rename = "network")] Network { - code: u32, - kind: NetworkErrorType, + code: ErrorCode, message: String, retry_after_ms: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -24,8 +23,7 @@ pub enum ClientApiError { /// Protocol layer error (client needs to check protocol compatibility) #[serde(rename = "protocol")] Protocol { - code: u32, - kind: ProtocolErrorType, + code: ErrorCode, message: String, #[serde(skip_serializing_if = "Option::is_none")] supported_versions: Option>, @@ -33,17 +31,12 @@ pub enum ClientApiError { /// Storage layer error (internal problem on the server) #[serde(rename = "storage")] - Storage { - code: u32, - kind: StorageErrorType, - message: String, - }, + Storage { code: ErrorCode, message: String }, /// Business logic error (client needs to adjust behavior) #[serde(rename = "business")] Business { - code: u32, - kind: BusinessErrorType, + code: ErrorCode, message: String, #[serde(skip_serializing_if = "Option::is_none")] required_action: Option, @@ -52,57 +45,56 @@ pub enum ClientApiError { /// General Client API error #[serde(rename = "general")] General { - code: u32, - kind: GeneralErrorType, + code: ErrorCode, message: String, #[serde(skip_serializing_if = "Option::is_none")] required_action: Option, }, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum NetworkErrorType { - Timeout, - ConnectionLost, - InvalidAddress, - TlsFailure, - ProtocolViolation, - JoinError, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ProtocolErrorType { - InvalidResponseFormat, - VersionMismatch, - ChecksumFailure, - SerializationError, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum StorageErrorType { - DiskFull, - CorruptionDetected, - IoFailure, - PermissionDenied, - KeyNotExist, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum BusinessErrorType { - NotLeader, - StaleRead, - InvalidRequest, - RateLimited, - ClusterUnavailable, - ProposeFailed, - RetryRequired, - StaleTerm, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum GeneralErrorType { - General, -} +// #[derive(Debug, Clone, Serialize, Deserialize)] +// pub enum NetworkErrorType { +// Timeout, +// ConnectionLost, +// InvalidAddress, +// TlsFailure, +// ProtocolViolation, +// JoinError, +// } + +// #[derive(Debug, Clone, Serialize, Deserialize)] +// pub enum ProtocolErrorType { +// InvalidResponseFormat, +// VersionMismatch, +// ChecksumFailure, +// SerializationError, +// } + +// #[derive(Debug, Clone, Serialize, Deserialize)] +// pub enum StorageErrorType { +// DiskFull, +// CorruptionDetected, +// IoFailure, +// PermissionDenied, +// KeyNotExist, +// } + +// #[derive(Debug, Clone, Serialize, Deserialize)] +// pub enum BusinessErrorType { +// NotLeader, +// StaleRead, +// InvalidRequest, +// RateLimited, +// ClusterUnavailable, +// ProposeFailed, +// RetryRequired, +// StaleTerm, +// } + +// #[derive(Debug, Clone, Serialize, Deserialize)] +// pub enum GeneralErrorType { +// General, +// } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LeaderInfo { pub id: String, @@ -110,45 +102,46 @@ pub struct LeaderInfo { pub last_contact: u64, // Unix timestamp in ms } impl From for ClientApiError { + /// Converts a tonic transport error into a ClientApiError + /// + /// This implementation handles different transport error scenarios: + /// - Connection timeouts + /// - Invalid URI/address formats + /// - Unexpected connection loss + /// + /// # Parameters + /// - `err`: The tonic transport error to convert + /// + /// # Returns + /// A ClientApiError with appropriate error code and retry information fn from(err: tonic::transport::Error) -> Self { - let (kind, message, retry) = match err { - e if e - .source() - .and_then(|e| e.downcast_ref::()) - .map(|e| e.kind() == std::io::ErrorKind::TimedOut) - .unwrap_or(false) => - { - ( - NetworkErrorType::Timeout, - format!("Connection timeout: {e}"), - Some(3000), // Retry after 3 seconds - ) + // Determine the error details based on the underlying error + if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::()) { + if io_err.kind() == std::io::ErrorKind::TimedOut { + return Self::Network { + code: ErrorCode::ConnectionTimeout, + message: format!("Connection timeout: {err}"), + retry_after_ms: Some(3000), // Retry after 3 seconds + leader_hint: None, + }; } - e if e.to_string().contains("invalid uri") => ( - NetworkErrorType::InvalidAddress, - format!("Invalid address: {e}"), - None, - ), - _ => ( - NetworkErrorType::ConnectionLost, - "Connection unexpectedly closed".into(), - Some(5000), - ), - }; - - // Convert NetworkErrorType to an appropriate ErrorCode - let code = match kind { - NetworkErrorType::Timeout => ErrorCode::ConnectionTimeout, - NetworkErrorType::InvalidAddress => ErrorCode::InvalidAddress, - NetworkErrorType::ConnectionLost => ErrorCode::ConnectionTimeout, - _ => ErrorCode::Uncategorized, - }; - - ClientApiError::Network { - code: code as u32, - kind, - message, - retry_after_ms: retry, + } + + // Check for invalid address errors + if err.to_string().contains("invalid uri") { + return Self::Network { + code: ErrorCode::InvalidAddress, + message: format!("Invalid address: {err}"), + retry_after_ms: None, // Not retryable - needs address correction + leader_hint: None, + }; + } + + // Default case: unexpected connection loss + Self::Network { + code: ErrorCode::ConnectionTimeout, + message: "Connection unexpectedly closed".into(), + retry_after_ms: Some(5000), // Retry after 5 seconds leader_hint: None, } } @@ -161,15 +154,13 @@ impl From for ClientApiError { match code { Code::Unavailable => Self::Business { - code: ErrorCode::ClusterUnavailable as u32, - kind: BusinessErrorType::ClusterUnavailable, + code: ErrorCode::ClusterUnavailable, message, required_action: Some("Retry after cluster recovery".into()), }, Code::Cancelled => Self::Network { - code: ErrorCode::ConnectionTimeout as u32, - kind: NetworkErrorType::Timeout, + code: ErrorCode::ConnectionTimeout, message, leader_hint: None, retry_after_ms: Some(1000), @@ -178,16 +169,14 @@ impl From for ClientApiError { Code::FailedPrecondition => { if let Some(leader) = parse_leader_from_metadata(&status) { Self::Network { - code: ErrorCode::LeaderChanged as u32, - kind: NetworkErrorType::ProtocolViolation, + code: ErrorCode::LeaderChanged, message: "Leadership changed".into(), retry_after_ms: Some(1000), leader_hint: Some(leader), } } else { Self::Business { - code: ErrorCode::StaleOperation as u32, - kind: BusinessErrorType::StaleRead, + code: ErrorCode::StaleOperation, message, required_action: Some("Refresh cluster state".into()), } @@ -195,22 +184,19 @@ impl From for ClientApiError { } Code::InvalidArgument => Self::Business { - code: ErrorCode::InvalidRequest as u32, - kind: BusinessErrorType::InvalidRequest, + code: ErrorCode::InvalidRequest, message, required_action: None, }, Code::PermissionDenied => Self::Business { - code: ErrorCode::NotLeader as u32, - kind: BusinessErrorType::NotLeader, + code: ErrorCode::NotLeader, message, required_action: Some("Refresh cluster state".into()), }, _ => Self::Business { - code: ErrorCode::Uncategorized as u32, - kind: BusinessErrorType::InvalidRequest, + code: ErrorCode::Uncategorized, message: format!("Unhandled status code: {code:?}"), required_action: None, }, @@ -218,6 +204,38 @@ impl From for ClientApiError { } } +// impl ClientApiError { +// pub fn error_category(&self) -> ErrorCategory { +// match self.code() { +// ErrorCode::ConnectionTimeout +// | ErrorCode::InvalidAddress +// | ErrorCode::LeaderChanged +// | ErrorCode::JoinError => ErrorCategory::Network, + +// ErrorCode::InvalidResponse | ErrorCode::VersionMismatch => ErrorCategory::Protocol, + +// ErrorCode::DiskFull +// | ErrorCode::DataCorruption +// | ErrorCode::StorageIoError +// | ErrorCode::StoragePermissionDenied +// | ErrorCode::KeyNotExist => ErrorCategory::Storage, + +// ErrorCode::NotLeader +// | ErrorCode::StaleOperation +// | ErrorCode::InvalidRequest +// | ErrorCode::RateLimited +// | ErrorCode::ClusterUnavailable +// | ErrorCode::ProposeFailed +// | ErrorCode::TermOutdated +// | ErrorCode::RetryRequired => ErrorCategory::Business, + +// ErrorCode::General | ErrorCode::Uncategorized => ErrorCategory::General, + +// _ => ErrorCategory::General, +// } +// } +// } + fn parse_leader_from_metadata(status: &Status) -> Option { status .metadata() @@ -261,29 +279,25 @@ impl From for ClientApiError { match code { // Network layer errors ErrorCode::ConnectionTimeout => ClientApiError::Network { - code: code as u32, - kind: NetworkErrorType::Timeout, + code, message: "Connection timeout".to_string(), retry_after_ms: None, leader_hint: None, }, ErrorCode::InvalidAddress => ClientApiError::Network { - code: code as u32, - kind: NetworkErrorType::InvalidAddress, + code, message: "Invalid address".to_string(), retry_after_ms: None, leader_hint: None, }, ErrorCode::LeaderChanged => ClientApiError::Network { - code: code as u32, - kind: NetworkErrorType::ConnectionLost, + code, message: "Leader changed".to_string(), retry_after_ms: Some(100), // suggest immediate retry leader_hint: None, // Note: This would ideally be populated from context }, ErrorCode::JoinError => ClientApiError::Network { - code: code as u32, - kind: NetworkErrorType::JoinError, + code, message: "Task Join Error".to_string(), retry_after_ms: Some(100), // suggest immediate retry leader_hint: None, // Note: This would ideally be populated from context @@ -291,105 +305,88 @@ impl From for ClientApiError { // Protocol layer errors ErrorCode::InvalidResponse => ClientApiError::Protocol { - code: code as u32, - kind: ProtocolErrorType::InvalidResponseFormat, + code, message: "Invalid response format".to_string(), supported_versions: None, }, ErrorCode::VersionMismatch => ClientApiError::Protocol { - code: code as u32, - kind: ProtocolErrorType::VersionMismatch, + code, message: "Version mismatch".to_string(), supported_versions: None, // Note: This would ideally be populated from context }, // Storage layer errors ErrorCode::DiskFull => ClientApiError::Storage { - code: code as u32, - kind: StorageErrorType::DiskFull, + code, message: "Disk full".to_string(), }, ErrorCode::DataCorruption => ClientApiError::Storage { - code: code as u32, - kind: StorageErrorType::CorruptionDetected, + code, message: "Data corruption detected".to_string(), }, ErrorCode::StorageIoError => ClientApiError::Storage { - code: code as u32, - kind: StorageErrorType::IoFailure, + code, message: "Storage I/O error".to_string(), }, ErrorCode::StoragePermissionDenied => ClientApiError::Storage { - code: code as u32, - kind: StorageErrorType::PermissionDenied, + code, message: "Storage permission denied".to_string(), }, ErrorCode::KeyNotExist => ClientApiError::Storage { - code: code as u32, - kind: StorageErrorType::KeyNotExist, + code, message: "Key not exist in storage".to_string(), }, // Business logic errors ErrorCode::NotLeader => ClientApiError::Business { - code: code as u32, - kind: BusinessErrorType::NotLeader, + code, message: "Not leader".to_string(), required_action: Some("redirect to leader".to_string()), }, ErrorCode::StaleOperation => ClientApiError::Business { - code: code as u32, - kind: BusinessErrorType::StaleRead, + code, message: "Stale operation".to_string(), required_action: Some("refresh state and retry".to_string()), }, ErrorCode::InvalidRequest => ClientApiError::Business { - code: code as u32, - kind: BusinessErrorType::InvalidRequest, + code, message: "Invalid request".to_string(), required_action: Some("check request parameters".to_string()), }, ErrorCode::RateLimited => ClientApiError::Business { - code: code as u32, - kind: BusinessErrorType::RateLimited, + code, message: "Rate limited".to_string(), required_action: Some("wait and retry".to_string()), }, ErrorCode::ClusterUnavailable => ClientApiError::Business { - code: code as u32, - kind: BusinessErrorType::ClusterUnavailable, + code, message: "Cluster unavailable".to_string(), required_action: Some("try again later".to_string()), }, ErrorCode::ProposeFailed => ClientApiError::Business { - code: code as u32, - kind: BusinessErrorType::ProposeFailed, + code, message: "Propose failed".to_string(), required_action: Some("try again later".to_string()), }, ErrorCode::Uncategorized => ClientApiError::Business { - code: code as u32, - kind: BusinessErrorType::InvalidRequest, + code, message: "Uncategorized error".to_string(), required_action: None, }, ErrorCode::TermOutdated => ClientApiError::Business { - code: code as u32, - kind: BusinessErrorType::StaleTerm, + code, message: "Stale term error".to_string(), required_action: None, }, ErrorCode::RetryRequired => ClientApiError::Business { - code: code as u32, - kind: BusinessErrorType::RetryRequired, + code, message: "Retry required. Please try again.".to_string(), required_action: None, }, // Unclassified error ErrorCode::General => ClientApiError::General { - code: code as u32, - kind: GeneralErrorType::General, + code, message: "General Client Api error".to_string(), required_action: None, }, @@ -401,7 +398,7 @@ impl From for ClientApiError { impl ClientApiError { /// Returns the error code associated with this error - pub fn code(&self) -> u32 { + pub fn code(&self) -> ErrorCode { match self { ClientApiError::Network { code, .. } => *code, ClientApiError::Protocol { code, .. } => *code, @@ -437,8 +434,7 @@ impl From for ClientApiError { impl ClientApiError { pub fn general_client_error(message: String) -> Self { ClientApiError::General { - code: ErrorCode::General as u32, - kind: GeneralErrorType::General, + code: ErrorCode::General, message, required_action: None, } diff --git a/src/client/kv.rs b/d-engine-client/src/kv.rs similarity index 95% rename from src/client/kv.rs rename to d-engine-client/src/kv.rs index 02eb9976..2b5d4f88 100644 --- a/src/client/kv.rs +++ b/d-engine-client/src/kv.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use arc_swap::ArcSwap; use bytes::Bytes; -use rand::rngs::StdRng; use rand::Rng; use rand::SeedableRng; +use rand::rngs::StdRng; use tonic::codec::CompressionEncoding; use tonic::transport::Channel; use tracing::debug; @@ -12,15 +12,17 @@ use tracing::error; use tracing::warn; use super::ClientInner; -use crate::proto::client::raft_client_service_client::RaftClientServiceClient; -use crate::proto::client::ClientReadRequest; -use crate::proto::client::ClientResult; -use crate::proto::client::ClientWriteRequest; -use crate::proto::client::ReadConsistencyPolicy; -use crate::proto::client::WriteCommand; -use crate::proto::error::ErrorCode; -use crate::scoped_timer::ScopedTimer; use crate::ClientApiError; +use crate::client_extensions::ClientResponseExt; +use crate::client_extensions::WriteCommandExt; +use crate::scoped_timer::ScopedTimer; +use d_engine_proto::client::ClientReadRequest; +use d_engine_proto::client::ClientResult; +use d_engine_proto::client::ClientWriteRequest; +use d_engine_proto::client::ReadConsistencyPolicy; +use d_engine_proto::client::WriteCommand; +use d_engine_proto::client::raft_client_service_client::RaftClientServiceClient; +use d_engine_proto::error::ErrorCode; /// Key-value store client interface /// diff --git a/src/client/kv_test.rs b/d-engine-client/src/kv_test.rs similarity index 97% rename from src/client/kv_test.rs rename to d-engine-client/src/kv_test.rs index e0062951..7b88e7fb 100644 --- a/src/client/kv_test.rs +++ b/d-engine-client/src/kv_test.rs @@ -3,24 +3,25 @@ use std::vec; use arc_swap::ArcSwap; use bytes::Bytes; +use d_engine_proto::common::NodeRole; use tokio::sync::oneshot; use tracing_test::traced_test; -use crate::proto::client::ClientResponse; -use crate::proto::client::ClientResult; -use crate::proto::client::ReadConsistencyPolicy; -use crate::proto::cluster::ClusterMembership; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::NodeStatus; -use crate::proto::error::ErrorCode; -use crate::test_utils::MockNode; use crate::Client; use crate::ClientConfig; use crate::ClientInner; use crate::ClusterClient; use crate::ConnectionPool; use crate::KvClient; -use crate::LEADER; +use crate::client_extensions::ClientResponseExt; +use crate::test_utils::MockNode; +use d_engine_proto::client::ClientResponse; +use d_engine_proto::client::ClientResult; +use d_engine_proto::client::ReadConsistencyPolicy; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::error::ErrorCode; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::NodeMeta; #[tokio::test] #[traced_test] @@ -94,10 +95,7 @@ async fn test_put_failure() { ) .await; assert!(result.is_err()); - assert_eq!( - result.unwrap_err().code(), - ErrorCode::ConnectionTimeout as u32 - ); + assert_eq!(result.unwrap_err().code(), ErrorCode::ConnectionTimeout); } #[tokio::test] @@ -136,11 +134,11 @@ async fn test_get_success() { // Set up mock response let mut leader_node = NodeMeta { id: 1, - role: LEADER, + role: NodeRole::Leader.into(), address: "127.0.0.1:50051".to_string(), status: NodeStatus::Active.into(), }; - leader_node.role = LEADER; + leader_node.role = NodeRole::Leader.into(); let result = client.get_with_policy(key, Some(ReadConsistencyPolicy::LinearizableRead)).await; println!("{:?}", &result); @@ -248,10 +246,7 @@ async fn test_delete_failure() { let result = client.delete(key).await; assert!(result.is_err()); - assert_eq!( - result.unwrap_err().code(), - ErrorCode::ConnectionTimeout as u32 - ); + assert_eq!(result.unwrap_err().code(), ErrorCode::ConnectionTimeout); } #[tokio::test] #[traced_test] @@ -431,10 +426,7 @@ async fn test_get_multi_failure() { .await; assert!(result.is_err()); - assert_eq!( - result.unwrap_err().code(), - ErrorCode::ConnectionTimeout as u32 - ); + assert_eq!(result.unwrap_err().code(), ErrorCode::ConnectionTimeout); } } @@ -471,7 +463,7 @@ async fn test_get_multi_empty_keys() { .await; assert!(result.is_err()); - assert_eq!(result.unwrap_err().code(), ErrorCode::InvalidRequest as u32); + assert_eq!(result.unwrap_err().code(), ErrorCode::InvalidRequest); } #[tokio::test] @@ -751,7 +743,7 @@ async fn test_get_consistency_methods_failure() { assert!(result.is_err(), "{method_name} should fail",); assert_eq!( result.unwrap_err().code(), - ErrorCode::ConnectionTimeout as u32, + ErrorCode::ConnectionTimeout, "{method_name} should return ConnectionTimeout", ); } @@ -1081,5 +1073,5 @@ async fn test_client_refresh_preserves_kv_and_cluster_clients() { let cluster_result = client.cluster().list_members().await; assert!(cluster_result.is_ok() || cluster_result.is_err()); // Either result is fine as long as - // it doesn't panic + // it doesn't panic } diff --git a/d-engine-client/src/lib.rs b/d-engine-client/src/lib.rs new file mode 100644 index 00000000..d0435052 --- /dev/null +++ b/d-engine-client/src/lib.rs @@ -0,0 +1,153 @@ +//! Client module for distributed consensus system +//! +//! Provides core components for interacting with the d_engine cluster: +//! - [`Client`] - Main entry point with cluster access +//! - [`ClientBuilder`] - Configurable client construction +//! - [`KvClient`] - Key-value store operations +//! - [`ClusterClient`] - Cluster management operations +//! - [`ConnectionPool`] - Underlying connection management +//! +//! # Basic Usage +//! ```no_run +//! use d_engine::client::{Client, ClientBuilder}; +//! use std::time::Duration; +//! use core::error::Error; +//! +//! #[tokio::main(flavor = "current_thread")] +//! async fn main(){ +//! // Initialize client with automatic cluster discovery +//! let client = Client::builder(vec![ +//! "http://node1:9081".into(), +//! "http://node2:9082".into() +//! ]) +//! .connect_timeout(Duration::from_secs(3)) +//! .request_timeout(Duration::from_secs(1)) +//! .enable_compression(true) +//! .build() +//! .await +//! .unwrap(); +//! +//! // Execute key-value operations +//! client.kv().put("user:1001", "Alice").await.unwrap(); +//! +//! let value = client.kv().get("user:1001").await.unwrap(); +//! +//! println!("User data: {:?}", value); +//! +//! // Perform cluster management +//! let members = client.cluster().list_members().await.unwrap(); +//! println!("Cluster members: {:?}", members); +//! +//! } +//! ``` + +mod builder; +mod client_extensions; +mod cluster; +mod config; +mod error; +mod kv; +mod pool; +pub use builder::*; +pub use cluster::*; +pub use config::*; +pub use error::*; +pub use kv::*; +pub use pool::*; + +#[cfg(test)] +mod cluster_test; +#[cfg(test)] +mod kv_test; +#[cfg(test)] +mod pool_test; +mod scoped_timer; +mod utils; + +use arc_swap::ArcSwap; + +/// Main entry point for interacting with the d_engine cluster +/// +/// Manages connections and provides access to specialized clients: +/// - Use [`kv()`](Client::kv) for data operations +/// - Use [`cluster()`](Client::cluster) for cluster administration +/// +/// Created through the [`builder()`](Client::builder) method +#[derive(Clone)] +pub struct Client { + /// Key-value store client interface + kv: KvClient, + + /// Cluster management client interface + cluster: ClusterClient, + + inner: std::sync::Arc>, +} + +#[derive(Clone)] +pub struct ClientInner { + pool: ConnectionPool, + client_id: u32, + config: ClientConfig, + endpoints: Vec, +} + +impl Client { + /// Access the key-value operations client + /// + /// # Examples + /// ```rust,ignore + /// client.kv().put("key", "value").await?; + /// ``` + pub fn kv(&self) -> &KvClient { + &self.kv + } + + /// Access the cluster management client + /// + /// # Examples + /// ```rust,ignore + /// client.cluster().add_node("node3:9083").await?; + /// ``` + pub fn cluster(&self) -> &ClusterClient { + &self.cluster + } + + /// Create a configured client builder + /// + /// Starts client construction process with specified bootstrap endpoints. + /// Chain configuration methods before calling + /// [`build()`](ClientBuilder::build). + /// + /// # Arguments + /// * `endpoints` - Initial cluster nodes for discovery + /// + /// # Panics + /// Will panic if no valid endpoints provided + pub fn builder(endpoints: Vec) -> ClientBuilder { + assert!(!endpoints.is_empty(), "At least one endpoint required"); + ClientBuilder::new(endpoints) + } + + pub async fn refresh( + &mut self, + new_endpoints: Option>, + ) -> std::result::Result<(), ClientApiError> { + // Get a writable lock + let old_inner = self.inner.load(); + let config = old_inner.config.clone(); + let endpoints = new_endpoints.unwrap_or(old_inner.endpoints.clone()); + + let new_pool = ConnectionPool::create(endpoints.clone(), config.clone()).await?; + + let new_inner = std::sync::Arc::new(ClientInner { + pool: new_pool, + client_id: old_inner.client_id, + config, + endpoints, + }); + + self.inner.store(new_inner); + Ok(()) + } +} diff --git a/src/client/pool.rs b/d-engine-client/src/pool.rs similarity index 94% rename from src/client/pool.rs rename to d-engine-client/src/pool.rs index daeb8e16..29c00a5e 100644 --- a/src/client/pool.rs +++ b/d-engine-client/src/pool.rs @@ -1,3 +1,4 @@ +use d_engine_proto::common::NodeRole; use tonic::codec::CompressionEncoding; use tonic::transport::Channel; use tonic::transport::Endpoint; @@ -6,12 +7,12 @@ use tracing::error; use tracing::info; use super::ClientApiError; -use crate::net::address_str; -use crate::proto::cluster::cluster_management_service_client::ClusterManagementServiceClient; -use crate::proto::cluster::MetadataRequest; -use crate::proto::cluster::NodeMeta; -use crate::proto::error::ErrorCode; use crate::ClientConfig; +use crate::utils::address_str; +use d_engine_proto::error::ErrorCode; +use d_engine_proto::server::cluster::MetadataRequest; +use d_engine_proto::server::cluster::NodeMeta; +use d_engine_proto::server::cluster::cluster_management_service_client::ClusterManagementServiceClient; /// Manages connections to cluster nodes /// @@ -181,7 +182,7 @@ impl ConnectionPool { for node in nodes { let addr = address_str(&node.address); debug!("parse_cluster_metadata, addr: {:?}", &addr); - if node.role == crate::LEADER { + if node.role == NodeRole::Leader.into() { leader_addr = Some(addr); } else { followers.push(addr); diff --git a/src/client/pool_test.rs b/d-engine-client/src/pool_test.rs similarity index 91% rename from src/client/pool_test.rs rename to d-engine-client/src/pool_test.rs index 0d335318..07c05bb9 100644 --- a/src/client/pool_test.rs +++ b/d-engine-client/src/pool_test.rs @@ -1,19 +1,18 @@ use std::time::Duration; use std::vec; +use d_engine_proto::common::NodeRole; use tokio::sync::oneshot; use tracing_test::traced_test; -use crate::proto::cluster::ClusterMembership; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::NodeStatus; -use crate::proto::error::ErrorCode; -use crate::test_utils::MockNode; -use crate::time::get_now_as_u32; use crate::ClientConfig; use crate::ConnectionPool; -use crate::FOLLOWER; -use crate::LEADER; +use crate::utils::get_now_as_u32; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::error::ErrorCode; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::NodeMeta; +use d_engine_test_utils::MockNode; #[tokio::test] #[traced_test] @@ -21,13 +20,13 @@ async fn test_parse_cluster_metadata_success() { let nodes = vec![ NodeMeta { id: 1, - role: LEADER, + role: NodeRole::Leader.into(), address: "127.0.0.1:50051".to_string(), status: NodeStatus::Active.into(), }, NodeMeta { id: 2, - role: FOLLOWER, + role: NodeRole::Follower.into(), address: "127.0.0.1:50052".to_string(), status: NodeStatus::Active.into(), }, @@ -43,14 +42,14 @@ async fn test_parse_cluster_metadata_success() { async fn test_parse_cluster_metadata_no_leader() { let nodes = vec![NodeMeta { id: 1, - role: FOLLOWER, + role: NodeRole::Follower.into(), address: "127.0.0.1:50051".to_string(), status: NodeStatus::Active.into(), }]; let result = ConnectionPool::parse_cluster_metadata(&nodes); let e = result.unwrap_err(); - assert_eq!(e.code(), ErrorCode::NotLeader as u32); + assert_eq!(e.code(), ErrorCode::NotLeader); } #[tokio::test] @@ -168,7 +167,7 @@ async fn test_refresh_successful_leader_change() { version: 1, nodes: vec![NodeMeta { id: leader_id, - role: LEADER, + role: NodeRole::Leader.into(), address: format!("127.0.0.1:{port}",), status: NodeStatus::Active.into(), }], @@ -202,7 +201,7 @@ async fn test_refresh_successful_leader_change() { version: 1, nodes: vec![NodeMeta { id: new_leader_id, - role: LEADER, + role: NodeRole::Leader.into(), address: format!("127.0.0.1:{port}",), status: NodeStatus::Active.into(), }], diff --git a/d-engine-client/src/scoped_timer.rs b/d-engine-client/src/scoped_timer.rs new file mode 100644 index 00000000..b2a80820 --- /dev/null +++ b/d-engine-client/src/scoped_timer.rs @@ -0,0 +1,23 @@ +use tokio::time::Instant; +use tracing::debug; + +pub(crate) struct ScopedTimer { + start: Instant, + name: &'static str, +} + +impl ScopedTimer { + pub(crate) fn new(name: &'static str) -> Self { + Self { + start: Instant::now(), + name, + } + } +} + +impl Drop for ScopedTimer { + fn drop(&mut self) { + let elapsed = self.start.elapsed(); + debug!(target: "timing", "[TIMING] {} took {} ms", self.name, elapsed.as_millis()); + } +} diff --git a/d-engine-client/src/utils.rs b/d-engine-client/src/utils.rs new file mode 100644 index 00000000..52e4d7ad --- /dev/null +++ b/d-engine-client/src/utils.rs @@ -0,0 +1,22 @@ +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +/// Returns seconds since epoch as u32 +/// # Warning +/// This will overflow on 2038-01-19 (Year 2038 problem) +#[inline] +pub fn get_now_as_u32() -> u32 { + get_duration_since_epoch().as_secs() as u32 +} + +#[inline] +pub fn get_duration_since_epoch() -> Duration { + SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards") +} + +/// accept ip either like 127.0.0.1 or docker host name: node1 +pub(crate) fn address_str(addr: &str) -> String { + // Strip existing "http://" or "https://" prefixes if duplicated. + let normalized = addr.trim_start_matches("http://").trim_start_matches("https://"); + // Re-add a single "http://" prefix (or use HTTPS if needed). + format!("http://{normalized}") +} diff --git a/d-engine-core/Cargo.toml b/d-engine-core/Cargo.toml new file mode 100644 index 00000000..2996eb76 --- /dev/null +++ b/d-engine-core/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "d-engine-core" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/d-engine-core/src/lib.rs b/d-engine-core/src/lib.rs new file mode 100644 index 00000000..18963301 --- /dev/null +++ b/d-engine-core/src/lib.rs @@ -0,0 +1,17 @@ +pub fn add( + left: u64, + right: u64, +) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/d-engine-proto/Cargo.toml b/d-engine-proto/Cargo.toml new file mode 100644 index 00000000..0cdf1c0d --- /dev/null +++ b/d-engine-proto/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "d-engine-proto" +version = "0.2.0" +edition = "2021" + +[dependencies] +# prost = { workspace = true } +prost = { version = "0.13", features = ["derive"] } +tonic = { workspace = true } +serde = { workspace = true } +bytes = { workspace = true } +tracing = { workspace = true } + + +[build-dependencies] +tonic-build = "0.12" +vergen = { version = "8.1", features = ["git", "gitcl"] } diff --git a/build.rs b/d-engine-proto/build.rs similarity index 73% rename from build.rs rename to d-engine-proto/build.rs index cd584fea..a7d1cf3c 100644 --- a/build.rs +++ b/d-engine-proto/build.rs @@ -8,11 +8,11 @@ fn main() -> Result<(), Box> { &[ "proto/common.proto", "proto/error.proto", - "proto/raft_election.proto", - "proto/raft_replication.proto", - "proto/cluster_management.proto", - "proto/storage.proto", - "proto/client_api.proto", + "proto/server/election.proto", + "proto/server/replication.proto", + "proto/server/cluster.proto", + "proto/server/storage.proto", + "proto/client/client_api.proto", ], &["."], ) diff --git a/proto/client_api.proto b/d-engine-proto/proto/client/client_api.proto similarity index 95% rename from proto/client_api.proto rename to d-engine-proto/proto/client/client_api.proto index f3b18dd4..fd0af951 100644 --- a/proto/client_api.proto +++ b/d-engine-proto/proto/client/client_api.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package raft.client; +package d_engine.client; import "proto/error.proto"; @@ -65,14 +65,14 @@ message ClientReadRequest { } message ClientResponse { - error.ErrorCode error = 1; + d_engine.error.ErrorCode error = 1; oneof success_result { bool write_ack = 2; ReadResults read_data = 3; } - error.ErrorMetadata metadata = 4; + d_engine.error.ErrorMetadata metadata = 4; } message ClientResult { // Renamed from ClientGetResult diff --git a/proto/common.proto b/d-engine-proto/proto/common.proto similarity index 95% rename from proto/common.proto rename to d-engine-proto/proto/common.proto index 58f144ea..2e1b23c8 100644 --- a/proto/common.proto +++ b/d-engine-proto/proto/common.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package raft.common; +package d_engine.common; // Basic definitions shared across modules message LogId { @@ -95,3 +95,10 @@ message Snapshot { SnapshotMeta meta = 1; repeated SnapshotEntry data = 2; } + + +enum NodeRole { + FOLLOWER = 0; + CANDIDATE = 1; + LEADER = 2; +} diff --git a/proto/error.proto b/d-engine-proto/proto/error.proto similarity index 79% rename from proto/error.proto rename to d-engine-proto/proto/error.proto index 26a40ab5..5b6748ec 100644 --- a/proto/error.proto +++ b/d-engine-proto/proto/error.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package raft.error; +package d_engine.error; enum ErrorCode { @@ -9,9 +9,9 @@ enum ErrorCode { // ====================== // Network layer error (1000-1999) // ====================== - CONNECTION_TIMEOUT = 1001; + CONNECTION_TIMEOUT = 1001; INVALID_ADDRESS = 1002; - LEADER_CHANGED = 1003; + LEADER_CHANGED = 1003; JOIN_ERROR = 1004; // ====================== @@ -23,8 +23,8 @@ enum ErrorCode { // ====================== // Storage layer error (3000-3999) // ====================== - DISK_FULL = 3001; - DATA_CORRUPTION = 3002; + DISK_FULL = 3001; + DATA_CORRUPTION = 3002; STORAGE_IO_ERROR = 3003; STORAGE_PERMISSION_DENIED = 3004; KEY_NOT_EXIST = 3005; @@ -32,20 +32,20 @@ enum ErrorCode { // ====================== // Business logic error (4000-4999) // ====================== - NOT_LEADER = 4001; - STALE_OPERATION = 4002; - INVALID_REQUEST = 4003; - RATE_LIMITED = 4004; - CLUSTER_UNAVAILABLE = 4005; + NOT_LEADER = 4001; + STALE_OPERATION = 4002; + INVALID_REQUEST = 4003; + RATE_LIMITED = 4004; + CLUSTER_UNAVAILABLE = 4005; PROPOSE_FAILED = 4006; TERM_OUTDATED = 4007; RETRY_REQUIRED = 4008; - + // ====================== // Unclassified error // ====================== GENERAL = 8888; - UNCATEGORIZED = 9999; + UNCATEGORIZED = 9999; } message ErrorMetadata { @@ -58,4 +58,4 @@ message ErrorMetadata { // Error details (for debugging only) optional string debug_message = 15; -} \ No newline at end of file +} diff --git a/proto/cluster_management.proto b/d-engine-proto/proto/server/cluster.proto similarity index 91% rename from proto/cluster_management.proto rename to d-engine-proto/proto/server/cluster.proto index b679b357..cd5ac13a 100644 --- a/proto/cluster_management.proto +++ b/d-engine-proto/proto/server/cluster.proto @@ -1,8 +1,8 @@ syntax = "proto3"; -package raft.cluster; +package d_engine.server.cluster; import "proto/common.proto"; -import "proto/storage.proto"; +import "proto/server/storage.proto"; //Configuration change request specifies the operation type @@ -18,7 +18,7 @@ message ClusterConfChangeRequest { // value is timestamp by default. uint64 version = 3; - raft.common.MembershipChange change = 4; + d_engine.common.MembershipChange change = 4; } message ClusterConfUpdateResponse { @@ -51,7 +51,7 @@ message NodeMeta { uint32 id = 1; string address = 2; //"ip:port" int32 role = 3; - raft.common.NodeStatus status = 4; // Add new status fields (such as active/draining) + d_engine.common.NodeStatus status = 4; // Add new status fields (such as active/draining) } // Request from new node to join the cluster @@ -93,7 +93,7 @@ message JoinResponse { // If the new node needs to receive a snapshot, this field contains the snapshot metadata // If no snapshot is needed, this field is empty - optional raft.storage.SnapshotMetadata snapshot_metadata = 5; + optional d_engine.server.storage.SnapshotMetadata snapshot_metadata = 5; // The ID of the current leader (if it exists), the new node can connect to the leader to get the snapshot uint32 leader_id = 6; diff --git a/proto/raft_election.proto b/d-engine-proto/proto/server/election.proto similarity index 86% rename from proto/raft_election.proto rename to d-engine-proto/proto/server/election.proto index cf2a45ec..8bcfeefc 100644 --- a/proto/raft_election.proto +++ b/d-engine-proto/proto/server/election.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package raft.election; +package d_engine.server.election; message VoteRequest { uint64 term = 1; @@ -16,10 +16,10 @@ message VoteResponse { } message VotedFor { - uint32 voted_for_id = 1; + uint32 voted_for_id = 1; uint64 voted_for_term = 2; } service RaftElectionService { rpc RequestVote (VoteRequest) returns (VoteResponse); -} \ No newline at end of file +} diff --git a/proto/raft_replication.proto b/d-engine-proto/proto/server/replication.proto similarity index 84% rename from proto/raft_replication.proto rename to d-engine-proto/proto/server/replication.proto index 1df2ebd8..b2449830 100644 --- a/proto/raft_replication.proto +++ b/d-engine-proto/proto/server/replication.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package raft.replication; +package d_engine.server.replication; import "proto/common.proto"; @@ -8,7 +8,7 @@ message AppendEntriesRequest { uint32 leaderId = 2; uint64 prevLogIndex = 3; uint64 prevLogTerm = 4; - repeated common.Entry entries = 5; + repeated common.Entry entries = 5; uint64 leaderCommitIndex = 6; } @@ -23,7 +23,7 @@ message AppendEntriesResponse { } message SuccessResult { - raft.common.LogId last_match = 1; + d_engine.common.LogId last_match = 1; } message ConflictResult { @@ -33,4 +33,4 @@ message ConflictResult { service RaftReplicationService { rpc AppendEntries (AppendEntriesRequest) returns (AppendEntriesResponse); -} \ No newline at end of file +} diff --git a/proto/storage.proto b/d-engine-proto/proto/server/storage.proto similarity index 86% rename from proto/storage.proto rename to d-engine-proto/proto/server/storage.proto index 7c5c9090..9f1a56b8 100644 --- a/proto/storage.proto +++ b/d-engine-proto/proto/server/storage.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package raft.storage; +package d_engine.server.storage; import "proto/common.proto"; @@ -27,7 +27,7 @@ message SnapshotChunk { } message SnapshotMetadata { - raft.common.LogId last_included = 1; // The last log index included in the snapshot + d_engine.common.LogId last_included = 1; // The last log index included in the snapshot bytes checksum = 2; // SHA-256 checksum// Total number of chunks in this snapshot } @@ -41,7 +41,7 @@ message PurgeLogRequest { uint64 term = 1; uint32 leader_id = 2; uint64 leader_commit = 3; // Leader's commit_index (for Peer verification) - raft.common.LogId last_included = 4; // Maximum log index that can be deleted + d_engine.common.LogId last_included = 4; // Maximum log index that can be deleted bytes snapshot_checksum = 5; } @@ -49,20 +49,20 @@ message PurgeLogResponse { uint32 node_id = 1; uint64 term = 2; // Peer's current term (for Leader abdication) bool success = 3; // Whether to accept the request (non-asynchronous operation result) - raft.common.LogId last_purged = 4; // Peer's current purged position (for Leader update progress) + d_engine.common.LogId last_purged = 4; // Peer's current purged position (for Leader update progress) } message SnapshotAck { // Received chunk sequence number uint32 seq = 1; - + // Status of this chunk ChunkStatus status = 2; - + // Next requested chunk (for flow control) uint32 next_requested = 3; - + enum ChunkStatus { ACCEPTED = 0; CHECKSUM_MISMATCH = 1; @@ -81,4 +81,4 @@ service SnapshotService { // Log compaction request rpc PurgeLog(PurgeLogRequest) returns (PurgeLogResponse); -} \ No newline at end of file +} diff --git a/src/generated/raft.client.rs b/d-engine-proto/src/generated/d_engine.client.rs similarity index 96% rename from src/generated/raft.client.rs rename to d-engine-proto/src/generated/d_engine.client.rs index 9357a134..db31c0a1 100644 --- a/src/generated/raft.client.rs +++ b/d-engine-proto/src/generated/d_engine.client.rs @@ -246,12 +246,15 @@ pub mod raft_client_service_client { })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( - "/raft.client.RaftClientService/HandleClientWrite", + "/d_engine.client.RaftClientService/HandleClientWrite", ); let mut req = request.into_request(); req.extensions_mut() .insert( - GrpcMethod::new("raft.client.RaftClientService", "HandleClientWrite"), + GrpcMethod::new( + "d_engine.client.RaftClientService", + "HandleClientWrite", + ), ); self.inner.unary(req, path, codec).await } @@ -269,12 +272,15 @@ pub mod raft_client_service_client { })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( - "/raft.client.RaftClientService/HandleClientRead", + "/d_engine.client.RaftClientService/HandleClientRead", ); let mut req = request.into_request(); req.extensions_mut() .insert( - GrpcMethod::new("raft.client.RaftClientService", "HandleClientRead"), + GrpcMethod::new( + "d_engine.client.RaftClientService", + "HandleClientRead", + ), ); self.inner.unary(req, path, codec).await } @@ -378,7 +384,7 @@ pub mod raft_client_service_server { } fn call(&mut self, req: http::Request) -> Self::Future { match req.uri().path() { - "/raft.client.RaftClientService/HandleClientWrite" => { + "/d_engine.client.RaftClientService/HandleClientWrite" => { #[allow(non_camel_case_types)] struct HandleClientWriteSvc(pub Arc); impl< @@ -427,7 +433,7 @@ pub mod raft_client_service_server { }; Box::pin(fut) } - "/raft.client.RaftClientService/HandleClientRead" => { + "/d_engine.client.RaftClientService/HandleClientRead" => { #[allow(non_camel_case_types)] struct HandleClientReadSvc(pub Arc); impl< @@ -509,7 +515,7 @@ pub mod raft_client_service_server { } } /// Generated gRPC service name - pub const SERVICE_NAME: &str = "raft.client.RaftClientService"; + pub const SERVICE_NAME: &str = "d_engine.client.RaftClientService"; impl tonic::server::NamedService for RaftClientServiceServer { const NAME: &'static str = SERVICE_NAME; } diff --git a/src/generated/raft.error.rs b/d-engine-proto/src/generated/d_engine.common.error.rs similarity index 100% rename from src/generated/raft.error.rs rename to d-engine-proto/src/generated/d_engine.common.error.rs diff --git a/src/generated/raft.common.rs b/d-engine-proto/src/generated/d_engine.common.rs similarity index 86% rename from src/generated/raft.common.rs rename to d-engine-proto/src/generated/d_engine.common.rs index 6454b568..da105668 100644 --- a/src/generated/raft.common.rs +++ b/d-engine-proto/src/generated/d_engine.common.rs @@ -193,3 +193,33 @@ impl NodeStatus { } } } +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum NodeRole { + Follower = 0, + Candidate = 1, + Leader = 2, +} +impl NodeRole { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Follower => "FOLLOWER", + Self::Candidate => "CANDIDATE", + Self::Leader => "LEADER", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "FOLLOWER" => Some(Self::Follower), + "CANDIDATE" => Some(Self::Candidate), + "LEADER" => Some(Self::Leader), + _ => None, + } + } +} diff --git a/d-engine-proto/src/generated/d_engine.error.rs b/d-engine-proto/src/generated/d_engine.error.rs new file mode 100644 index 00000000..55f2eebe --- /dev/null +++ b/d-engine-proto/src/generated/d_engine.error.rs @@ -0,0 +1,119 @@ +// This file is @generated by prost-build. +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ErrorMetadata { + /// Errors suitable for retry + #[prost(uint64, optional, tag = "1")] + pub retry_after_ms: ::core::option::Option, + /// Leader change information + #[prost(string, optional, tag = "2")] + pub leader_id: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag = "3")] + pub leader_address: ::core::option::Option<::prost::alloc::string::String>, + /// Error details (for debugging only) + #[prost(string, optional, tag = "15")] + pub debug_message: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum ErrorCode { + /// Success status (non-error) + Success = 0, + /// ====================== + /// Network layer error (1000-1999) + /// ====================== + ConnectionTimeout = 1001, + InvalidAddress = 1002, + LeaderChanged = 1003, + JoinError = 1004, + /// ====================== + /// Protocol layer error (2000-2999) + /// ====================== + InvalidResponse = 2001, + VersionMismatch = 2002, + /// ====================== + /// Storage layer error (3000-3999) + /// ====================== + DiskFull = 3001, + DataCorruption = 3002, + StorageIoError = 3003, + StoragePermissionDenied = 3004, + KeyNotExist = 3005, + /// ====================== + /// Business logic error (4000-4999) + /// ====================== + NotLeader = 4001, + StaleOperation = 4002, + InvalidRequest = 4003, + RateLimited = 4004, + ClusterUnavailable = 4005, + ProposeFailed = 4006, + TermOutdated = 4007, + RetryRequired = 4008, + /// ====================== + /// Unclassified error + /// ====================== + General = 8888, + Uncategorized = 9999, +} +impl ErrorCode { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Success => "SUCCESS", + Self::ConnectionTimeout => "CONNECTION_TIMEOUT", + Self::InvalidAddress => "INVALID_ADDRESS", + Self::LeaderChanged => "LEADER_CHANGED", + Self::JoinError => "JOIN_ERROR", + Self::InvalidResponse => "INVALID_RESPONSE", + Self::VersionMismatch => "VERSION_MISMATCH", + Self::DiskFull => "DISK_FULL", + Self::DataCorruption => "DATA_CORRUPTION", + Self::StorageIoError => "STORAGE_IO_ERROR", + Self::StoragePermissionDenied => "STORAGE_PERMISSION_DENIED", + Self::KeyNotExist => "KEY_NOT_EXIST", + Self::NotLeader => "NOT_LEADER", + Self::StaleOperation => "STALE_OPERATION", + Self::InvalidRequest => "INVALID_REQUEST", + Self::RateLimited => "RATE_LIMITED", + Self::ClusterUnavailable => "CLUSTER_UNAVAILABLE", + Self::ProposeFailed => "PROPOSE_FAILED", + Self::TermOutdated => "TERM_OUTDATED", + Self::RetryRequired => "RETRY_REQUIRED", + Self::General => "GENERAL", + Self::Uncategorized => "UNCATEGORIZED", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SUCCESS" => Some(Self::Success), + "CONNECTION_TIMEOUT" => Some(Self::ConnectionTimeout), + "INVALID_ADDRESS" => Some(Self::InvalidAddress), + "LEADER_CHANGED" => Some(Self::LeaderChanged), + "JOIN_ERROR" => Some(Self::JoinError), + "INVALID_RESPONSE" => Some(Self::InvalidResponse), + "VERSION_MISMATCH" => Some(Self::VersionMismatch), + "DISK_FULL" => Some(Self::DiskFull), + "DATA_CORRUPTION" => Some(Self::DataCorruption), + "STORAGE_IO_ERROR" => Some(Self::StorageIoError), + "STORAGE_PERMISSION_DENIED" => Some(Self::StoragePermissionDenied), + "KEY_NOT_EXIST" => Some(Self::KeyNotExist), + "NOT_LEADER" => Some(Self::NotLeader), + "STALE_OPERATION" => Some(Self::StaleOperation), + "INVALID_REQUEST" => Some(Self::InvalidRequest), + "RATE_LIMITED" => Some(Self::RateLimited), + "CLUSTER_UNAVAILABLE" => Some(Self::ClusterUnavailable), + "PROPOSE_FAILED" => Some(Self::ProposeFailed), + "TERM_OUTDATED" => Some(Self::TermOutdated), + "RETRY_REQUIRED" => Some(Self::RetryRequired), + "GENERAL" => Some(Self::General), + "UNCATEGORIZED" => Some(Self::Uncategorized), + _ => None, + } + } +} diff --git a/src/generated/raft.cluster.rs b/d-engine-proto/src/generated/d_engine.server.cluster.rs similarity index 96% rename from src/generated/raft.cluster.rs rename to d-engine-proto/src/generated/d_engine.server.cluster.rs index a3bfccfd..4e6b2585 100644 --- a/src/generated/raft.cluster.rs +++ b/d-engine-proto/src/generated/d_engine.server.cluster.rs @@ -15,7 +15,7 @@ pub struct ClusterConfChangeRequest { #[prost(uint64, tag = "3")] pub version: u64, #[prost(message, optional, tag = "4")] - pub change: ::core::option::Option, + pub change: ::core::option::Option, } #[derive(serde::Serialize, serde::Deserialize)] #[derive(Clone, Copy, PartialEq, ::prost::Message)] @@ -111,7 +111,7 @@ pub struct NodeMeta { #[prost(int32, tag = "3")] pub role: i32, /// Add new status fields (such as active/draining) - #[prost(enumeration = "super::common::NodeStatus", tag = "4")] + #[prost(enumeration = "super::super::common::NodeStatus", tag = "4")] pub status: i32, } /// Request from new node to join the cluster @@ -318,13 +318,13 @@ pub mod cluster_management_service_client { })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( - "/raft.cluster.ClusterManagementService/UpdateClusterConf", + "/d_engine.server.cluster.ClusterManagementService/UpdateClusterConf", ); let mut req = request.into_request(); req.extensions_mut() .insert( GrpcMethod::new( - "raft.cluster.ClusterManagementService", + "d_engine.server.cluster.ClusterManagementService", "UpdateClusterConf", ), ); @@ -347,13 +347,13 @@ pub mod cluster_management_service_client { })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( - "/raft.cluster.ClusterManagementService/GetClusterMetadata", + "/d_engine.server.cluster.ClusterManagementService/GetClusterMetadata", ); let mut req = request.into_request(); req.extensions_mut() .insert( GrpcMethod::new( - "raft.cluster.ClusterManagementService", + "d_engine.server.cluster.ClusterManagementService", "GetClusterMetadata", ), ); @@ -374,13 +374,13 @@ pub mod cluster_management_service_client { })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( - "/raft.cluster.ClusterManagementService/JoinCluster", + "/d_engine.server.cluster.ClusterManagementService/JoinCluster", ); let mut req = request.into_request(); req.extensions_mut() .insert( GrpcMethod::new( - "raft.cluster.ClusterManagementService", + "d_engine.server.cluster.ClusterManagementService", "JoinCluster", ), ); @@ -404,13 +404,13 @@ pub mod cluster_management_service_client { })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( - "/raft.cluster.ClusterManagementService/DiscoverLeader", + "/d_engine.server.cluster.ClusterManagementService/DiscoverLeader", ); let mut req = request.into_request(); req.extensions_mut() .insert( GrpcMethod::new( - "raft.cluster.ClusterManagementService", + "d_engine.server.cluster.ClusterManagementService", "DiscoverLeader", ), ); @@ -536,7 +536,7 @@ pub mod cluster_management_service_server { } fn call(&mut self, req: http::Request) -> Self::Future { match req.uri().path() { - "/raft.cluster.ClusterManagementService/UpdateClusterConf" => { + "/d_engine.server.cluster.ClusterManagementService/UpdateClusterConf" => { #[allow(non_camel_case_types)] struct UpdateClusterConfSvc(pub Arc); impl< @@ -585,7 +585,7 @@ pub mod cluster_management_service_server { }; Box::pin(fut) } - "/raft.cluster.ClusterManagementService/GetClusterMetadata" => { + "/d_engine.server.cluster.ClusterManagementService/GetClusterMetadata" => { #[allow(non_camel_case_types)] struct GetClusterMetadataSvc( pub Arc, @@ -636,7 +636,7 @@ pub mod cluster_management_service_server { }; Box::pin(fut) } - "/raft.cluster.ClusterManagementService/JoinCluster" => { + "/d_engine.server.cluster.ClusterManagementService/JoinCluster" => { #[allow(non_camel_case_types)] struct JoinClusterSvc(pub Arc); impl< @@ -685,7 +685,7 @@ pub mod cluster_management_service_server { }; Box::pin(fut) } - "/raft.cluster.ClusterManagementService/DiscoverLeader" => { + "/d_engine.server.cluster.ClusterManagementService/DiscoverLeader" => { #[allow(non_camel_case_types)] struct DiscoverLeaderSvc(pub Arc); impl< @@ -767,7 +767,7 @@ pub mod cluster_management_service_server { } } /// Generated gRPC service name - pub const SERVICE_NAME: &str = "raft.cluster.ClusterManagementService"; + pub const SERVICE_NAME: &str = "d_engine.server.cluster.ClusterManagementService"; impl tonic::server::NamedService for ClusterManagementServiceServer { const NAME: &'static str = SERVICE_NAME; } diff --git a/src/generated/raft.election.rs b/d-engine-proto/src/generated/d_engine.server.election.rs similarity index 96% rename from src/generated/raft.election.rs rename to d-engine-proto/src/generated/d_engine.server.election.rs index 27a1c0e1..5e9016b2 100644 --- a/src/generated/raft.election.rs +++ b/d-engine-proto/src/generated/d_engine.server.election.rs @@ -136,12 +136,15 @@ pub mod raft_election_service_client { })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( - "/raft.election.RaftElectionService/RequestVote", + "/d_engine.server.election.RaftElectionService/RequestVote", ); let mut req = request.into_request(); req.extensions_mut() .insert( - GrpcMethod::new("raft.election.RaftElectionService", "RequestVote"), + GrpcMethod::new( + "d_engine.server.election.RaftElectionService", + "RequestVote", + ), ); self.inner.unary(req, path, codec).await } @@ -241,7 +244,7 @@ pub mod raft_election_service_server { } fn call(&mut self, req: http::Request) -> Self::Future { match req.uri().path() { - "/raft.election.RaftElectionService/RequestVote" => { + "/d_engine.server.election.RaftElectionService/RequestVote" => { #[allow(non_camel_case_types)] struct RequestVoteSvc(pub Arc); impl< @@ -320,7 +323,7 @@ pub mod raft_election_service_server { } } /// Generated gRPC service name - pub const SERVICE_NAME: &str = "raft.election.RaftElectionService"; + pub const SERVICE_NAME: &str = "d_engine.server.election.RaftElectionService"; impl tonic::server::NamedService for RaftElectionServiceServer { const NAME: &'static str = SERVICE_NAME; } diff --git a/src/generated/raft.replication.rs b/d-engine-proto/src/generated/d_engine.server.replication.rs similarity index 96% rename from src/generated/raft.replication.rs rename to d-engine-proto/src/generated/d_engine.server.replication.rs index 25c277d2..d9eb6bf3 100644 --- a/src/generated/raft.replication.rs +++ b/d-engine-proto/src/generated/d_engine.server.replication.rs @@ -11,7 +11,7 @@ pub struct AppendEntriesRequest { #[prost(uint64, tag = "4")] pub prev_log_term: u64, #[prost(message, repeated, tag = "5")] - pub entries: ::prost::alloc::vec::Vec, + pub entries: ::prost::alloc::vec::Vec, #[prost(uint64, tag = "6")] pub leader_commit_index: u64, } @@ -42,7 +42,7 @@ pub mod append_entries_response { #[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct SuccessResult { #[prost(message, optional, tag = "1")] - pub last_match: ::core::option::Option, + pub last_match: ::core::option::Option, } #[derive(serde::Serialize, serde::Deserialize)] #[derive(Clone, Copy, PartialEq, ::prost::Message)] @@ -162,13 +162,13 @@ pub mod raft_replication_service_client { })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( - "/raft.replication.RaftReplicationService/AppendEntries", + "/d_engine.server.replication.RaftReplicationService/AppendEntries", ); let mut req = request.into_request(); req.extensions_mut() .insert( GrpcMethod::new( - "raft.replication.RaftReplicationService", + "d_engine.server.replication.RaftReplicationService", "AppendEntries", ), ); @@ -274,7 +274,7 @@ pub mod raft_replication_service_server { } fn call(&mut self, req: http::Request) -> Self::Future { match req.uri().path() { - "/raft.replication.RaftReplicationService/AppendEntries" => { + "/d_engine.server.replication.RaftReplicationService/AppendEntries" => { #[allow(non_camel_case_types)] struct AppendEntriesSvc(pub Arc); impl< @@ -356,7 +356,7 @@ pub mod raft_replication_service_server { } } /// Generated gRPC service name - pub const SERVICE_NAME: &str = "raft.replication.RaftReplicationService"; + pub const SERVICE_NAME: &str = "d_engine.server.replication.RaftReplicationService"; impl tonic::server::NamedService for RaftReplicationServiceServer { const NAME: &'static str = SERVICE_NAME; } diff --git a/src/generated/raft.storage.rs b/d-engine-proto/src/generated/d_engine.server.storage.rs similarity index 94% rename from src/generated/raft.storage.rs rename to d-engine-proto/src/generated/d_engine.server.storage.rs index d6155408..a5f3445d 100644 --- a/src/generated/raft.storage.rs +++ b/d-engine-proto/src/generated/d_engine.server.storage.rs @@ -29,7 +29,7 @@ pub struct SnapshotChunk { pub struct SnapshotMetadata { /// The last log index included in the snapshot #[prost(message, optional, tag = "1")] - pub last_included: ::core::option::Option, + pub last_included: ::core::option::Option, /// SHA-256 checksum// Total number of chunks in this snapshot #[prost(bytes = "bytes", tag = "2")] pub checksum: ::prost::bytes::Bytes, @@ -59,7 +59,7 @@ pub struct PurgeLogRequest { pub leader_commit: u64, /// Maximum log index that can be deleted #[prost(message, optional, tag = "4")] - pub last_included: ::core::option::Option, + pub last_included: ::core::option::Option, #[prost(bytes = "bytes", tag = "5")] pub snapshot_checksum: ::prost::bytes::Bytes, } @@ -76,7 +76,7 @@ pub struct PurgeLogResponse { pub success: bool, /// Peer's current purged position (for Leader update progress) #[prost(message, optional, tag = "4")] - pub last_purged: ::core::option::Option, + pub last_purged: ::core::option::Option, } #[derive(serde::Serialize, serde::Deserialize)] #[derive(Clone, Copy, PartialEq, ::prost::Message)] @@ -249,12 +249,15 @@ pub mod snapshot_service_client { })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( - "/raft.storage.SnapshotService/InstallSnapshot", + "/d_engine.server.storage.SnapshotService/InstallSnapshot", ); let mut req = request.into_streaming_request(); req.extensions_mut() .insert( - GrpcMethod::new("raft.storage.SnapshotService", "InstallSnapshot"), + GrpcMethod::new( + "d_engine.server.storage.SnapshotService", + "InstallSnapshot", + ), ); self.inner.client_streaming(req, path, codec).await } @@ -276,12 +279,15 @@ pub mod snapshot_service_client { })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( - "/raft.storage.SnapshotService/StreamSnapshot", + "/d_engine.server.storage.SnapshotService/StreamSnapshot", ); let mut req = request.into_streaming_request(); req.extensions_mut() .insert( - GrpcMethod::new("raft.storage.SnapshotService", "StreamSnapshot"), + GrpcMethod::new( + "d_engine.server.storage.SnapshotService", + "StreamSnapshot", + ), ); self.inner.streaming(req, path, codec).await } @@ -303,11 +309,16 @@ pub mod snapshot_service_client { })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( - "/raft.storage.SnapshotService/PurgeLog", + "/d_engine.server.storage.SnapshotService/PurgeLog", ); let mut req = request.into_request(); req.extensions_mut() - .insert(GrpcMethod::new("raft.storage.SnapshotService", "PurgeLog")); + .insert( + GrpcMethod::new( + "d_engine.server.storage.SnapshotService", + "PurgeLog", + ), + ); self.inner.unary(req, path, codec).await } } @@ -432,7 +443,7 @@ pub mod snapshot_service_server { } fn call(&mut self, req: http::Request) -> Self::Future { match req.uri().path() { - "/raft.storage.SnapshotService/InstallSnapshot" => { + "/d_engine.server.storage.SnapshotService/InstallSnapshot" => { #[allow(non_camel_case_types)] struct InstallSnapshotSvc(pub Arc); impl< @@ -480,7 +491,7 @@ pub mod snapshot_service_server { }; Box::pin(fut) } - "/raft.storage.SnapshotService/StreamSnapshot" => { + "/d_engine.server.storage.SnapshotService/StreamSnapshot" => { #[allow(non_camel_case_types)] struct StreamSnapshotSvc(pub Arc); impl< @@ -527,7 +538,7 @@ pub mod snapshot_service_server { }; Box::pin(fut) } - "/raft.storage.SnapshotService/PurgeLog" => { + "/d_engine.server.storage.SnapshotService/PurgeLog" => { #[allow(non_camel_case_types)] struct PurgeLogSvc(pub Arc); impl< @@ -605,7 +616,7 @@ pub mod snapshot_service_server { } } /// Generated gRPC service name - pub const SERVICE_NAME: &str = "raft.storage.SnapshotService"; + pub const SERVICE_NAME: &str = "d_engine.server.storage.SnapshotService"; impl tonic::server::NamedService for SnapshotServiceServer { const NAME: &'static str = SERVICE_NAME; } diff --git a/d-engine-proto/src/lib.rs b/d-engine-proto/src/lib.rs new file mode 100644 index 00000000..14f345e8 --- /dev/null +++ b/d-engine-proto/src/lib.rs @@ -0,0 +1,33 @@ +//! Protocol Buffer definitions and generated code for RPC services. +//! +//! This module contains auto-generated Rust types from Protobuf definitions, +//! typically created using [`tonic-build`] or `protoc` compiler plugins. + +pub mod common { + include!("generated/d_engine.common.rs"); +} + +pub mod error { + include!("generated/d_engine.common.error.rs"); +} +pub mod server { + pub mod cluster { + include!("generated/d_engine.server.cluster.rs"); + } + + pub mod replication { + include!("generated/d_engine.server.replication.rs"); + } + + pub mod election { + include!("generated/d_engine.server.election.rs"); + } + + pub mod storage { + include!("generated/d_engine.server.storage.rs"); + } +} + +pub mod client { + include!("generated/d_engine.client.rs"); +} diff --git a/d-engine-server/Cargo.toml b/d-engine-server/Cargo.toml new file mode 100644 index 00000000..d60c77e5 --- /dev/null +++ b/d-engine-server/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "d-engine-server" +version = "0.1.0" +edition = "2024" + +[dependencies] +rocksdb = { version = "0.24.0", optional = true } diff --git a/d-engine-server/src/main.rs b/d-engine-server/src/main.rs new file mode 100644 index 00000000..e7a11a96 --- /dev/null +++ b/d-engine-server/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/d-engine-test-utils/Cargo.toml b/d-engine-test-utils/Cargo.toml new file mode 100644 index 00000000..e65896a8 --- /dev/null +++ b/d-engine-test-utils/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "d-engine-test-utils" +version = "0.2.0" +edition = "2021" + +[dependencies] +d-engine-proto = { workspace = true } +tokio = { workspace = true } +tonic = { workspace = true } +tonic-health = { workspace = true } +tracing = { workspace = true } +bincode = { workspace = true } +tempfile = { workspace = true } +prost = { workspace = true } +async-compression = { workspace = true } +tokio-stream = { workspace = true } +tokio-tar = { workspace = true } +bytes = { workspace = true } +futures = { workspace = true } +http-body = { workspace = true } +http-body-util = { workspace = true } +crc32fast = { workspace = true } diff --git a/src/test_utils/common.rs b/d-engine-test-utils/src/common.rs similarity index 94% rename from src/test_utils/common.rs rename to d-engine-test-utils/src/common.rs index 9904cc4e..aac88fb6 100644 --- a/src/test_utils/common.rs +++ b/d-engine-test-utils/src/common.rs @@ -9,16 +9,16 @@ use prost::Message; use crate::alias::ROF; use crate::convert::safe_kv_bytes; -use crate::proto::client::WriteCommand; -use crate::proto::common::membership_change::Change; -use crate::proto::common::AddNode; -use crate::proto::common::Entry; -use crate::proto::common::EntryPayload; use crate::FileStorageEngine; use crate::MockStateMachine; use crate::RaftLog; use crate::RaftTypeConfig; use crate::SnapshotConfig; +use d_engine_proto::client::WriteCommand; +use d_engine_proto::common::membership_change::Change; +use d_engine_proto::common::AddNode; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; pub fn create_mixed_entries() -> Vec { let config_entry = Entry { diff --git a/src/test_utils/entry_builder.rs b/d-engine-test-utils/src/entry_builder.rs similarity index 89% rename from src/test_utils/entry_builder.rs rename to d-engine-test-utils/src/entry_builder.rs index 1fdee32a..85f2968f 100644 --- a/src/test_utils/entry_builder.rs +++ b/d-engine-test-utils/src/entry_builder.rs @@ -1,8 +1,8 @@ use bytes::Bytes; -use crate::proto::common::membership_change::Change; -use crate::proto::common::Entry; -use crate::proto::common::EntryPayload; +use d_engine_proto::common::membership_change::Change; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; pub struct EntryBuilder { index: u64, diff --git a/src/test_utils/integration/mod.rs b/d-engine-test-utils/src/integration/mod.rs similarity index 97% rename from src/test_utils/integration/mod.rs rename to d-engine-test-utils/src/integration/mod.rs index afe594c1..222da708 100644 --- a/src/test_utils/integration/mod.rs +++ b/d-engine-test-utils/src/integration/mod.rs @@ -51,10 +51,6 @@ use crate::alias::ROF; use crate::alias::SMOF; use crate::alias::TROF; use crate::grpc::grpc_transport::GrpcTransport; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::Entry; -use crate::proto::common::EntryPayload; -use crate::proto::common::NodeStatus; use crate::test_utils::mock_state_machine; use crate::test_utils::MockTypeConfig; use crate::BufferedRaftLog; @@ -76,6 +72,10 @@ use crate::ReplicationHandler; use crate::StateMachine; use crate::StorageEngine; use crate::TypeConfig; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::server::cluster::NodeMeta; #[allow(dead_code)] pub struct TestContext diff --git a/src/test_utils/integration/snapshot.rs b/d-engine-test-utils/src/integration/snapshot.rs similarity index 98% rename from src/test_utils/integration/snapshot.rs rename to d-engine-test-utils/src/integration/snapshot.rs index 157a9fdb..8c0de680 100644 --- a/src/test_utils/integration/snapshot.rs +++ b/d-engine-test-utils/src/integration/snapshot.rs @@ -19,12 +19,12 @@ use tonic::Code; use tonic::Status; use tracing::debug; -use crate::proto::common::LogId; -use crate::proto::storage::SnapshotChunk; -use crate::proto::storage::SnapshotMetadata; use crate::stream::GrpcStreamDecoder; use crate::NetworkError; use crate::Result; +use d_engine_proto::common::LogId; +use d_engine_proto::server::storage::SnapshotChunk; +use d_engine_proto::server::storage::SnapshotMetadata; /// Helper to create a valid snapshot stream pub(crate) fn create_snapshot_stream( diff --git a/src/test_utils/mod.rs b/d-engine-test-utils/src/lib.rs similarity index 100% rename from src/test_utils/mod.rs rename to d-engine-test-utils/src/lib.rs diff --git a/src/test_utils/mock/mock_builder.rs b/d-engine-test-utils/src/mock/mock_builder.rs similarity index 99% rename from src/test_utils/mock/mock_builder.rs rename to d-engine-test-utils/src/mock/mock_builder.rs index a10a30e5..8f4c8e17 100644 --- a/src/test_utils/mock/mock_builder.rs +++ b/d-engine-test-utils/src/mock/mock_builder.rs @@ -12,8 +12,6 @@ use tracing::trace; use super::MockTypeConfig; use crate::follower_state::FollowerState; use crate::grpc; -use crate::proto::cluster::ClusterMembership; -use crate::proto::common::LogId; use crate::ElectionConfig; use crate::MockElectionCore; use crate::MockMembership; @@ -36,6 +34,8 @@ use crate::RaftStorageHandles; use crate::RoleEvent; use crate::SignalParams; use crate::StateMachine; +use d_engine_proto::common::LogId; +use d_engine_proto::server::cluster::ClusterMembership; pub struct MockBuilder { pub id: Option, diff --git a/src/test_utils/mock/mock_rpc.rs b/d-engine-test-utils/src/mock/mock_rpc.rs similarity index 83% rename from src/test_utils/mock/mock_rpc.rs rename to d-engine-test-utils/src/mock/mock_rpc.rs index 70155010..a58f423c 100644 --- a/src/test_utils/mock/mock_rpc.rs +++ b/d-engine-test-utils/src/mock/mock_rpc.rs @@ -4,32 +4,32 @@ use tokio_stream::StreamExt; use tonic::Streaming; use tracing::trace; -use crate::proto::client::raft_client_service_server::RaftClientService; -use crate::proto::client::ClientReadRequest; -use crate::proto::client::ClientResponse; -use crate::proto::client::ClientWriteRequest; -use crate::proto::cluster::cluster_management_service_server::ClusterManagementService; -use crate::proto::cluster::ClusterConfChangeRequest; -use crate::proto::cluster::ClusterConfUpdateResponse; -use crate::proto::cluster::ClusterMembership; -use crate::proto::cluster::JoinRequest; -use crate::proto::cluster::JoinResponse; -use crate::proto::cluster::LeaderDiscoveryRequest; -use crate::proto::cluster::LeaderDiscoveryResponse; -use crate::proto::cluster::MetadataRequest; -use crate::proto::election::raft_election_service_server::RaftElectionService; -use crate::proto::election::VoteRequest; -use crate::proto::election::VoteResponse; -use crate::proto::replication::raft_replication_service_server::RaftReplicationService; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::replication::AppendEntriesResponse; -use crate::proto::storage::snapshot_service_server::SnapshotService; -use crate::proto::storage::PurgeLogRequest; -use crate::proto::storage::PurgeLogResponse; -use crate::proto::storage::SnapshotAck; -use crate::proto::storage::SnapshotChunk; -use crate::proto::storage::SnapshotResponse; use crate::test_utils::crate_test_snapshot_stream; +use d_engine_proto::client::raft_client_service_server::RaftClientService; +use d_engine_proto::client::ClientReadRequest; +use d_engine_proto::client::ClientResponse; +use d_engine_proto::client::ClientWriteRequest; +use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementService; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::JoinRequest; +use d_engine_proto::server::cluster::JoinResponse; +use d_engine_proto::server::cluster::LeaderDiscoveryRequest; +use d_engine_proto::server::cluster::LeaderDiscoveryResponse; +use d_engine_proto::server::cluster::MetadataRequest; +use d_engine_proto::server::election::raft_election_service_server::RaftElectionService; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationService; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::storage::snapshot_service_server::SnapshotService; +use d_engine_proto::server::storage::PurgeLogRequest; +use d_engine_proto::server::storage::PurgeLogResponse; +use d_engine_proto::server::storage::SnapshotAck; +use d_engine_proto::server::storage::SnapshotChunk; +use d_engine_proto::server::storage::SnapshotResponse; #[derive(Clone, Default)] pub struct MockRpcService { diff --git a/src/test_utils/mock/mock_rpc_service.rs b/d-engine-test-utils/src/mock/mock_rpc_service.rs similarity index 92% rename from src/test_utils/mock/mock_rpc_service.rs rename to d-engine-test-utils/src/mock/mock_rpc_service.rs index 3fa4c80f..c0407b40 100644 --- a/src/test_utils/mock/mock_rpc_service.rs +++ b/d-engine-test-utils/src/mock/mock_rpc_service.rs @@ -11,23 +11,23 @@ use tracing::debug; use tracing::info; use super::MockRpcService; -use crate::proto::client::raft_client_service_server::RaftClientServiceServer; -use crate::proto::client::ClientResponse; -use crate::proto::cluster::cluster_management_service_server::ClusterManagementServiceServer; -use crate::proto::cluster::ClusterMembership; -use crate::proto::cluster::JoinResponse; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::NodeStatus; -use crate::proto::election::raft_election_service_server::RaftElectionServiceServer; -use crate::proto::election::VoteResponse; -use crate::proto::replication::raft_replication_service_server::RaftReplicationServiceServer; -use crate::proto::replication::AppendEntriesResponse; -use crate::proto::storage::snapshot_service_server::SnapshotServiceServer; -use crate::proto::storage::PurgeLogResponse; -use crate::proto::storage::SnapshotResponse; use crate::Result; use crate::StorageError; use crate::LEADER; +use d_engine_proto::client::raft_client_service_server::RaftClientServiceServer; +use d_engine_proto::client::ClientResponse; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementServiceServer; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::JoinResponse; +use d_engine_proto::server::cluster::NodeMeta; +use d_engine_proto::server::election::raft_election_service_server::RaftElectionServiceServer; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationServiceServer; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::storage::snapshot_service_server::SnapshotServiceServer; +use d_engine_proto::server::storage::PurgeLogResponse; +use d_engine_proto::server::storage::SnapshotResponse; // ports for unit tests pub(crate) const MOCK_STATE_MACHINE_HANDLER_PORT_BASE: u32 = 60400; diff --git a/src/test_utils/mock/mock_storage_engine.rs b/d-engine-test-utils/src/mock/mock_storage_engine.rs similarity index 100% rename from src/test_utils/mock/mock_storage_engine.rs rename to d-engine-test-utils/src/mock/mock_storage_engine.rs diff --git a/src/test_utils/mock/mod.rs b/d-engine-test-utils/src/mock/mod.rs similarity index 98% rename from src/test_utils/mock/mod.rs rename to d-engine-test-utils/src/mock/mod.rs index 03c4e25f..69120100 100644 --- a/src/test_utils/mock/mod.rs +++ b/d-engine-test-utils/src/mock/mod.rs @@ -51,10 +51,10 @@ use tokio::sync::watch; use super::node_config; use super::MockTypeConfig; -use crate::proto::cluster::NodeMeta; use crate::Node; use crate::Raft; use crate::RaftContext; +use d_engine_proto::server::cluster::NodeMeta; pub(crate) fn mock_node( db_path: &str, diff --git a/src/test_utils/mock_type_config.rs b/d-engine-test-utils/src/mock_type_config.rs similarity index 100% rename from src/test_utils/mock_type_config.rs rename to d-engine-test-utils/src/mock_type_config.rs diff --git a/src/client/mod.rs b/src/client/mod.rs deleted file mode 100644 index 2226d4d9..00000000 --- a/src/client/mod.rs +++ /dev/null @@ -1,339 +0,0 @@ -//! Client module for distributed consensus system -//! -//! Provides core components for interacting with the d_engine cluster: -//! - [`Client`] - Main entry point with cluster access -//! - [`ClientBuilder`] - Configurable client construction -//! - [`KvClient`] - Key-value store operations -//! - [`ClusterClient`] - Cluster management operations -//! - [`ConnectionPool`] - Underlying connection management -//! -//! # Basic Usage -//! ```no_run -//! use d_engine::client::{Client, ClientBuilder}; -//! use std::time::Duration; -//! use core::error::Error; -//! -//! #[tokio::main(flavor = "current_thread")] -//! async fn main(){ -//! // Initialize client with automatic cluster discovery -//! let client = Client::builder(vec![ -//! "http://node1:9081".into(), -//! "http://node2:9082".into() -//! ]) -//! .connect_timeout(Duration::from_secs(3)) -//! .request_timeout(Duration::from_secs(1)) -//! .enable_compression(true) -//! .build() -//! .await -//! .unwrap(); -//! -//! // Execute key-value operations -//! client.kv().put("user:1001", "Alice").await.unwrap(); -//! -//! let value = client.kv().get("user:1001").await.unwrap(); -//! -//! println!("User data: {:?}", value); -//! -//! // Perform cluster management -//! let members = client.cluster().list_members().await.unwrap(); -//! println!("Cluster members: {:?}", members); -//! -//! } -//! ``` - -mod builder; -mod cluster; -mod config; -mod error; -mod kv; -mod pool; - -pub use builder::*; -use bytes::Bytes; -pub use cluster::*; -pub use config::*; -pub use error::*; -pub use kv::*; -pub use pool::*; - -#[cfg(test)] -mod cluster_test; -#[cfg(test)] -mod kv_test; -#[cfg(test)] -mod pool_test; - -use std::sync::Arc; - -use arc_swap::ArcSwap; -use tracing::error; - -use crate::proto::client::client_response::SuccessResult; -use crate::proto::client::write_command; -use crate::proto::client::ClientResponse; -use crate::proto::client::ClientResult; -use crate::proto::client::ReadResults; -use crate::proto::client::WriteCommand; -use crate::proto::error::ErrorCode; - -/// Main entry point for interacting with the d_engine cluster -/// -/// Manages connections and provides access to specialized clients: -/// - Use [`kv()`](Client::kv) for data operations -/// - Use [`cluster()`](Client::cluster) for cluster administration -/// -/// Created through the [`builder()`](Client::builder) method -#[derive(Clone)] -pub struct Client { - /// Key-value store client interface - pub(super) kv: KvClient, - - /// Cluster management client interface - pub(super) cluster: ClusterClient, - - pub(super) inner: Arc>, -} - -#[derive(Clone)] -pub struct ClientInner { - pub(super) pool: ConnectionPool, - pub(super) client_id: u32, - pub(super) config: ClientConfig, - pub(super) endpoints: Vec, -} - -impl Client { - /// Access the key-value operations client - /// - /// # Examples - /// ```rust,ignore - /// client.kv().put("key", "value").await?; - /// ``` - pub fn kv(&self) -> &KvClient { - &self.kv - } - - /// Access the cluster management client - /// - /// # Examples - /// ```rust,ignore - /// client.cluster().add_node("node3:9083").await?; - /// ``` - pub fn cluster(&self) -> &ClusterClient { - &self.cluster - } - - /// Create a configured client builder - /// - /// Starts client construction process with specified bootstrap endpoints. - /// Chain configuration methods before calling - /// [`build()`](ClientBuilder::build). - /// - /// # Arguments - /// * `endpoints` - Initial cluster nodes for discovery - /// - /// # Panics - /// Will panic if no valid endpoints provided - pub fn builder(endpoints: Vec) -> ClientBuilder { - assert!(!endpoints.is_empty(), "At least one endpoint required"); - ClientBuilder::new(endpoints) - } - - pub async fn refresh( - &mut self, - new_endpoints: Option>, - ) -> std::result::Result<(), ClientApiError> { - // Get a writable lock - let old_inner = self.inner.load(); - let config = old_inner.config.clone(); - let endpoints = new_endpoints.unwrap_or(old_inner.endpoints.clone()); - - let new_pool = ConnectionPool::create(endpoints.clone(), config.clone()).await?; - - let new_inner = Arc::new(ClientInner { - pool: new_pool, - client_id: old_inner.client_id, - config, - endpoints, - }); - - self.inner.store(new_inner); - Ok(()) - } -} - -impl WriteCommand { - /// Create write command for key-value pair - /// - /// # Parameters - /// - `key`: Byte array for storage key - /// - `value`: Byte array to be stored - pub fn insert( - key: impl Into, - value: impl Into, - ) -> Self { - let cmd = write_command::Insert { - key: key.into(), - value: value.into(), - }; - Self { - operation: Some(write_command::Operation::Insert(cmd)), - } - } - - /// Create deletion command for specified key - /// - /// # Parameters - /// - `key`: Byte array of key to delete - pub fn delete(key: impl Into) -> Self { - let cmd = write_command::Delete { key: key.into() }; - Self { - operation: Some(write_command::Operation::Delete(cmd)), - } - } -} -impl ClientResponse { - /// Build success response for write operations - /// - /// # Returns - /// Response with NoError code and write confirmation - pub fn write_success() -> Self { - Self { - error: ErrorCode::Success as i32, - success_result: Some(SuccessResult::WriteAck(true)), - metadata: None, - } - } - - /// Check if the write operation was successful - /// - /// # Returns - /// - `true` if the response indicates a successful write operation - /// - `false` if the response indicates a failed write operation or is not a write response - pub fn is_write_success(&self) -> bool { - self.error == ErrorCode::Success as i32 - && matches!(self.success_result, Some(SuccessResult::WriteAck(true))) - } - - /// Build success response for read operations - /// - /// # Parameters - /// - `results`: Vector of retrieved key-value pairs - pub fn read_results(results: Vec) -> Self { - Self { - error: ErrorCode::Success as i32, - success_result: Some(SuccessResult::ReadData(ReadResults { results })), - metadata: None, - } - } - - /// Build generic error response for any operation type - /// - /// # Parameters - /// - `error_code`: Predefined client request error code - pub fn client_error(error_code: ErrorCode) -> Self { - Self { - error: error_code as i32, - success_result: None, - metadata: None, - } - } - - /// Convert response to boolean write result - /// - /// # Returns - /// - `Ok(true)` on successful write - /// - `Err` with converted error code on failure - pub fn into_write_result(&self) -> std::result::Result { - self.validate_error()?; - Ok(match self.success_result { - Some(SuccessResult::WriteAck(success)) => success, - _ => false, - }) - } - - /// Convert response to read results - /// - /// # Returns - /// Vector of optional key-value pairs wrapped in Result - pub fn into_read_results( - &self - ) -> std::result::Result>, ClientApiError> { - self.validate_error()?; - match &self.success_result { - Some(SuccessResult::ReadData(data)) => data - .results - .clone() - .into_iter() - .map(|item| { - Ok(Some(ClientResult { - key: item.key, - value: item.value, - })) - }) - .collect(), - _ => { - error!("Invalid response type for read operation"); - unreachable!() - } - } - } - - /// Validate error code in response header - /// - /// # Internal Logic - /// Converts numeric error code to enum variant - pub(crate) fn validate_error(&self) -> std::result::Result<(), ClientApiError> { - match ErrorCode::try_from(self.error).unwrap_or(ErrorCode::Uncategorized) { - ErrorCode::Success => Ok(()), - e => Err(e.into()), - } - } - - /// Check if this response indicates the leader's term is outdated - pub fn is_term_outdated(&self) -> bool { - ErrorCode::try_from(self.error).map(|e| e.is_term_outdated()).unwrap_or(false) - } - - /// Check if this response indicates a quorum timeout or failure to receive majority responses - pub fn is_quorum_timeout_or_failure(&self) -> bool { - ErrorCode::try_from(self.error) - .map(|e| e.is_quorum_timeout_or_failure()) - .unwrap_or(false) - } - - /// Check if this response indicates a failure to receive majority responses - pub fn is_propose_failure(&self) -> bool { - ErrorCode::try_from(self.error).map(|e| e.is_propose_failure()).unwrap_or(false) - } - - /// Check if this response indicates a a retry required - pub fn is_retry_required(&self) -> bool { - ErrorCode::try_from(self.error).map(|e| e.is_retry_required()).unwrap_or(false) - } -} - -impl ErrorCode { - /// Check if this error indicates the leader's term is outdated - pub(crate) fn is_term_outdated(&self) -> bool { - matches!(self, ErrorCode::TermOutdated) - } - - /// Check if this error indicates a quorum timeout or failure to receive majority responses - pub(crate) fn is_quorum_timeout_or_failure(&self) -> bool { - matches!( - self, - ErrorCode::ConnectionTimeout | ErrorCode::ProposeFailed | ErrorCode::ClusterUnavailable - ) - } - - /// Check if this error indicates a failure to receive majority responses - pub(crate) fn is_propose_failure(&self) -> bool { - matches!(self, ErrorCode::ProposeFailed) - } - - /// Check if this error indicates a retry required - pub(crate) fn is_retry_required(&self) -> bool { - matches!(self, ErrorCode::RetryRequired) - } -} diff --git a/src/proto/client_extensions.rs b/src/proto/client_extensions.rs deleted file mode 100644 index b5934d16..00000000 --- a/src/proto/client_extensions.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Extensions for protobuf-generated client types -//! -//! This module provides additional methods for protobuf-generated types -//! that are not automatically generated by the protobuf compiler. - -use crate::proto::client::ClientReadRequest; -use crate::proto::client::ReadConsistencyPolicy; - -impl ClientReadRequest { - /// Checks if the consistency_policy field is present in the request - /// - /// Returns true if the client explicitly specified a consistency policy, - /// false if the field is absent (should use server default). - pub fn has_consistency_policy(&self) -> bool { - self.consistency_policy.is_some() - } - - /// Gets the consistency policy value safely - /// - /// Returns Some(policy) if present, None if field is absent. - /// Safer alternative that doesn't panic. - pub fn get_consistency_policy(&self) -> Option { - self.consistency_policy.and_then(|policy_i32| { - match policy_i32 { - x if x == ReadConsistencyPolicy::LeaseRead as i32 => { - Some(ReadConsistencyPolicy::LeaseRead) - } - x if x == ReadConsistencyPolicy::LinearizableRead as i32 => { - Some(ReadConsistencyPolicy::LinearizableRead) - } - _ => None, // Invalid value - } - }) - } -} diff --git a/src/proto/mod.rs b/src/proto/mod.rs deleted file mode 100644 index e2875525..00000000 --- a/src/proto/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Protocol Buffer definitions and generated code for RPC services. -//! -//! This module contains auto-generated Rust types from Protobuf definitions, -//! typically created using [`tonic-build`] or `protoc` compiler plugins. - -pub mod common { - include!("../generated/raft.common.rs"); -} - -pub mod error { - include!("../generated/raft.error.rs"); -} - -pub mod cluster { - include!("../generated/raft.cluster.rs"); -} - -pub mod replication { - include!("../generated/raft.replication.rs"); -} - -pub mod election { - include!("../generated/raft.election.rs"); -} - -pub mod storage { - include!("../generated/raft.storage.rs"); -} - -pub mod client { - include!("../generated/raft.client.rs"); -} - -pub mod client_extensions; From c419d8ba159c8c87ac7379117058ed78bca1a766 Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Sat, 18 Oct 2025 21:00:59 +0800 Subject: [PATCH 02/26] feat #167: d-engine-client cleanup --- Cargo.lock | 142 +----------- Cargo.toml | 2 - d-engine-client/Cargo.toml | 3 +- d-engine-client/src/builder.rs | 3 +- d-engine-client/src/client_extensions.rs | 21 +- d-engine-client/src/cluster_test.rs | 2 +- d-engine-client/src/kv_test.rs | 2 +- d-engine-client/src/lib.rs | 16 +- d-engine-client/src/mock_rpc.rs | 129 +++++++++++ d-engine-client/src/mock_rpc_service.rs | 212 ++++++++++++++++++ d-engine-client/src/pool_test.rs | 2 +- d-engine-test-utils/Cargo.toml | 22 -- .../src => src/test_utils}/common.rs | 0 .../src => src/test_utils}/entry_builder.rs | 0 .../src => src/test_utils}/integration/mod.rs | 0 .../test_utils}/integration/snapshot.rs | 0 .../test_utils}/mock/mock_builder.rs | 0 .../src => src/test_utils}/mock/mock_rpc.rs | 0 .../test_utils}/mock/mock_rpc_service.rs | 0 .../test_utils}/mock/mock_storage_engine.rs | 0 .../src => src/test_utils}/mock/mod.rs | 0 .../test_utils}/mock_type_config.rs | 0 .../src/lib.rs => src/test_utils/mod.rs | 0 23 files changed, 380 insertions(+), 176 deletions(-) create mode 100644 d-engine-client/src/mock_rpc.rs create mode 100644 d-engine-client/src/mock_rpc_service.rs delete mode 100644 d-engine-test-utils/Cargo.toml rename {d-engine-test-utils/src => src/test_utils}/common.rs (100%) rename {d-engine-test-utils/src => src/test_utils}/entry_builder.rs (100%) rename {d-engine-test-utils/src => src/test_utils}/integration/mod.rs (100%) rename {d-engine-test-utils/src => src/test_utils}/integration/snapshot.rs (100%) rename {d-engine-test-utils/src => src/test_utils}/mock/mock_builder.rs (100%) rename {d-engine-test-utils/src => src/test_utils}/mock/mock_rpc.rs (100%) rename {d-engine-test-utils/src => src/test_utils}/mock/mock_rpc_service.rs (100%) rename {d-engine-test-utils/src => src/test_utils}/mock/mock_storage_engine.rs (100%) rename {d-engine-test-utils/src => src/test_utils}/mock/mod.rs (100%) rename {d-engine-test-utils/src => src/test_utils}/mock_type_config.rs (100%) rename d-engine-test-utils/src/lib.rs => src/test_utils/mod.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index a54e9524..5681033c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,19 +44,6 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" -[[package]] -name = "async-compression" -version = "0.4.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" -dependencies = [ - "compression-codecs", - "compression-core", - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -170,22 +157,13 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bindgen" version = "0.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f" dependencies = [ - "bitflags 2.9.3", + "bitflags", "cexpr", "clang-sys", "itertools 0.13.0", @@ -197,12 +175,6 @@ dependencies = [ "syn", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.9.3" @@ -265,23 +237,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "compression-codecs" -version = "0.4.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" -dependencies = [ - "compression-core", - "flate2", - "memchr", -] - -[[package]] -name = "compression-core" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" - [[package]] name = "crc32fast" version = "1.5.0" @@ -298,13 +253,14 @@ dependencies = [ "arc-swap", "bytes", "d-engine-proto", - "d-engine-test-utils", "futures", "mockall", "rand", "serde", "tokio", + "tokio-stream", "tonic", + "tonic-health", "tracing", "tracing-test", ] @@ -333,28 +289,6 @@ dependencies = [ "rocksdb", ] -[[package]] -name = "d-engine-test-utils" -version = "0.2.0" -dependencies = [ - "async-compression", - "bincode", - "bytes", - "crc32fast", - "d-engine-proto", - "futures", - "http-body", - "http-body-util", - "prost", - "tempfile", - "tokio", - "tokio-stream", - "tokio-tar", - "tonic", - "tonic-health", - "tracing", -] - [[package]] name = "deranged" version = "0.5.4" @@ -398,18 +332,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "filetime" -version = "0.2.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.60.2", -] - [[package]] name = "fixedbitset" version = "0.5.7" @@ -728,7 +650,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags 2.9.3", + "bitflags", "cfg-if", "libc", ] @@ -789,17 +711,6 @@ dependencies = [ "windows-targets 0.53.3", ] -[[package]] -name = "libredox" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" -dependencies = [ - "bitflags 2.9.3", - "libc", - "redox_syscall 0.5.18", -] - [[package]] name = "librocksdb-sys" version = "0.17.3+10.4.2" @@ -1194,24 +1105,6 @@ dependencies = [ "getrandom 0.2.16", ] -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.9.3", -] - [[package]] name = "regex" version = "1.11.2" @@ -1283,7 +1176,7 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.3", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1551,21 +1444,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-tar" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" -dependencies = [ - "filetime", - "futures-core", - "libc", - "redox_syscall 0.3.5", - "tokio", - "tokio-stream", - "xattr", -] - [[package]] name = "tokio-util" version = "0.7.16" @@ -2001,16 +1879,6 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" -[[package]] -name = "xattr" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" -dependencies = [ - "libc", - "rustix", -] - [[package]] name = "zerocopy" version = "0.8.26" diff --git a/Cargo.toml b/Cargo.toml index 90df195a..8565e2c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,6 @@ members = [ "d-engine-core", "d-engine-client", "d-engine-server", - "d-engine-test-utils", ] resolver = "2" @@ -14,7 +13,6 @@ d-engine-proto = { path = "./d-engine-proto" } d-engine-core = { path = "./d-engine-core" } d-engine-client = { path = "./d-engine-client" } d-engine-server = { path = "./d-engine-server" } -d-engine-test-utils = { path = "./d-engine-test-utils" } # [package] # name = "d-engine" diff --git a/d-engine-client/Cargo.toml b/d-engine-client/Cargo.toml index 87d0b8b7..ca9e9a24 100644 --- a/d-engine-client/Cargo.toml +++ b/d-engine-client/Cargo.toml @@ -8,14 +8,15 @@ d-engine-proto = { workspace = true } tokio = { workspace = true } tonic = { workspace = true } +tonic-health = { workspace = true } bytes = { workspace = true } tracing = { workspace = true } serde = { workspace = true } rand = { workspace = true } arc-swap = "1.7.1" futures = { workspace = true } +tokio-stream = { workspace = true } [dev-dependencies] -d-engine-test-utils = { workspace = true } mockall = "0.12.1" tracing-test = "0.2" diff --git a/d-engine-client/src/builder.rs b/d-engine-client/src/builder.rs index df8ed008..15d2b707 100644 --- a/d-engine-client/src/builder.rs +++ b/d-engine-client/src/builder.rs @@ -80,7 +80,8 @@ impl ClientBuilder { /// /// # Example: Full Configuration /// ```no_run - /// use d_engine::client::{ClientBuilder, ClientConfig}; + /// use d_engine_client::ClientBuilder; + /// use d_engine_client::ClientConfig; /// use std::time::Duration; /// /// let custom_config = ClientConfig { diff --git a/d-engine-client/src/client_extensions.rs b/d-engine-client/src/client_extensions.rs index 251dca03..e50de997 100644 --- a/d-engine-client/src/client_extensions.rs +++ b/d-engine-client/src/client_extensions.rs @@ -22,12 +22,14 @@ pub trait ClientReadRequestExt { /// /// Returns true if the client explicitly specified a consistency policy, /// false if the field is absent (should use server default). + #[allow(unused)] fn has_consistency_policy(&self) -> bool; /// Gets the consistency policy value safely /// /// Returns Some(policy) if present, None if field is absent. /// Safer alternative that doesn't panic. + #[allow(unused)] fn get_consistency_policy(&self) -> Option; } @@ -40,6 +42,7 @@ pub trait ClientResponseExt { /// /// # Returns /// Response with NoError code and write confirmation + #[allow(unused)] fn write_success() -> ClientResponse; /// Check if the write operation was successful @@ -47,18 +50,21 @@ pub trait ClientResponseExt { /// # Returns /// - `true` if the response indicates a successful write operation /// - `false` if the response indicates a failed write operation or is not a write response + #[allow(unused)] fn is_write_success(&self) -> bool; /// Build success response for read operations /// /// # Parameters /// - `results`: Vector of retrieved key-value pairs + #[allow(unused)] fn read_results(results: Vec) -> Self; /// Build generic error response for any operation type /// /// # Parameters /// - `error_code`: Predefined client request error code + #[allow(unused)] fn client_error(error_code: ErrorCode) -> Self; /// Convert response to boolean write result @@ -66,30 +72,37 @@ pub trait ClientResponseExt { /// # Returns /// - `Ok(true)` on successful write /// - `Err` with converted error code on failure - fn into_write_result(&self) -> std::result::Result; + #[allow(unused)] + fn into_write_result(self) -> std::result::Result; /// Convert response to read results /// /// # Returns /// Vector of optional key-value pairs wrapped in Result - fn into_read_results(&self) -> std::result::Result>, ClientApiError>; + #[allow(unused)] + fn into_read_results(self) -> std::result::Result>, ClientApiError>; /// Validate error code in response header /// /// # Internal Logic /// Converts numeric error code to enum variant + #[allow(unused)] fn validate_error(&self) -> std::result::Result<(), ClientApiError>; /// Check if this response indicates the leader's term is outdated + #[allow(unused)] fn is_term_outdated(&self) -> bool; /// Check if this response indicates a quorum timeout or failure to receive majority responses + #[allow(unused)] fn is_quorum_timeout_or_failure(&self) -> bool; /// Check if this response indicates a failure to receive majority responses + #[allow(unused)] fn is_propose_failure(&self) -> bool; /// Check if this response indicates a a retry required + #[allow(unused)] fn is_retry_required(&self) -> bool; } @@ -236,7 +249,7 @@ impl ClientResponseExt for ClientResponse { /// # Returns /// - `Ok(true)` on successful write /// - `Err` with converted error code on failure - fn into_write_result(&self) -> std::result::Result { + fn into_write_result(self) -> std::result::Result { self.validate_error()?; Ok(match self.success_result { Some(SuccessResult::WriteAck(success)) => success, @@ -248,7 +261,7 @@ impl ClientResponseExt for ClientResponse { /// /// # Returns /// Vector of optional key-value pairs wrapped in Result - fn into_read_results(&self) -> std::result::Result>, ClientApiError> { + fn into_read_results(self) -> std::result::Result>, ClientApiError> { self.validate_error()?; match &self.success_result { Some(SuccessResult::ReadData(data)) => data diff --git a/d-engine-client/src/cluster_test.rs b/d-engine-client/src/cluster_test.rs index 7634e812..b0b7a5af 100644 --- a/d-engine-client/src/cluster_test.rs +++ b/d-engine-client/src/cluster_test.rs @@ -11,7 +11,7 @@ use crate::ClientConfig; use crate::ClientInner; use crate::ClusterClient; use crate::ConnectionPool; -use crate::test_utils::MockNode; +use crate::mock_rpc_service::MockNode; use d_engine_proto::common::NodeStatus; use d_engine_proto::error::ErrorCode; use d_engine_proto::server::cluster::ClusterMembership; diff --git a/d-engine-client/src/kv_test.rs b/d-engine-client/src/kv_test.rs index 7b88e7fb..8ec0d1c9 100644 --- a/d-engine-client/src/kv_test.rs +++ b/d-engine-client/src/kv_test.rs @@ -14,7 +14,7 @@ use crate::ClusterClient; use crate::ConnectionPool; use crate::KvClient; use crate::client_extensions::ClientResponseExt; -use crate::test_utils::MockNode; +use crate::mock_rpc_service::MockNode; use d_engine_proto::client::ClientResponse; use d_engine_proto::client::ClientResult; use d_engine_proto::client::ReadConsistencyPolicy; diff --git a/d-engine-client/src/lib.rs b/d-engine-client/src/lib.rs index d0435052..fc2167a2 100644 --- a/d-engine-client/src/lib.rs +++ b/d-engine-client/src/lib.rs @@ -9,7 +9,8 @@ //! //! # Basic Usage //! ```no_run -//! use d_engine::client::{Client, ClientBuilder}; +//! use d_engine_client::Client; +//! use d_engine_client::ClientBuilder; //! use std::time::Duration; //! use core::error::Error; //! @@ -48,6 +49,9 @@ mod config; mod error; mod kv; mod pool; +mod scoped_timer; +mod utils; + pub use builder::*; pub use cluster::*; pub use config::*; @@ -60,11 +64,11 @@ mod cluster_test; #[cfg(test)] mod kv_test; #[cfg(test)] +mod mock_rpc; +#[cfg(test)] +mod mock_rpc_service; +#[cfg(test)] mod pool_test; -mod scoped_timer; -mod utils; - -use arc_swap::ArcSwap; /// Main entry point for interacting with the d_engine cluster /// @@ -81,7 +85,7 @@ pub struct Client { /// Cluster management client interface cluster: ClusterClient, - inner: std::sync::Arc>, + inner: std::sync::Arc>, } #[derive(Clone)] diff --git a/d-engine-client/src/mock_rpc.rs b/d-engine-client/src/mock_rpc.rs new file mode 100644 index 00000000..14c73508 --- /dev/null +++ b/d-engine-client/src/mock_rpc.rs @@ -0,0 +1,129 @@ +use d_engine_proto::client::ClientReadRequest; +use d_engine_proto::client::ClientResponse; +use d_engine_proto::client::ClientWriteRequest; +use d_engine_proto::client::raft_client_service_server::RaftClientService; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::JoinRequest; +use d_engine_proto::server::cluster::JoinResponse; +use d_engine_proto::server::cluster::LeaderDiscoveryRequest; +use d_engine_proto::server::cluster::LeaderDiscoveryResponse; +use d_engine_proto::server::cluster::MetadataRequest; +use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementService; +use std::sync::Arc; + +#[derive(Clone, Default)] +pub struct MockRpcService { + pub server_port: Option, + // Expected responses for each method + pub expected_update_cluster_conf_response: + Option>, + pub expected_client_propose_response: Option>, + pub expected_client_read_response: Option>, + + #[allow(clippy::type_complexity)] + pub expected_metadata_response: + Option Result + Send + Sync>>, + + pub expected_join_cluster_response: Option>, + pub expected_discover_leader_response: Option>, +} +impl MockRpcService { + pub fn with_metadata_response( + mut self, + f: impl Fn(u16) -> Result + Send + Sync + 'static, + ) -> Self { + self.expected_metadata_response = Some(Arc::new(f)); + self + } + + pub fn set_port( + &mut self, + port: u16, + ) { + self.server_port = Some(port); + } +} + +#[tonic::async_trait] +impl ClusterManagementService for MockRpcService { + async fn update_cluster_conf( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match &self.expected_update_cluster_conf_response { + Some(Ok(response)) => Ok(tonic::Response::new(*response)), + Some(Err(status)) => Err(status.clone()), + None => Err(tonic::Status::unknown( + "No mock update_cluster_conf response set", + )), + } + } + async fn get_cluster_metadata( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match (&self.expected_metadata_response, self.server_port) { + (Some(f), Some(port)) => f(port).map(tonic::Response::new).map_err(|e| e.clone()), + _ => Err(tonic::Status::unimplemented( + "Metadata response not configured", + )), + } + } + + async fn join_cluster( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match &self.expected_join_cluster_response { + Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), + Some(Err(status)) => Err(status.clone()), + None => Err(tonic::Status::unknown( + "No mock get_cluster_metadata response set", + )), + } + } + + async fn discover_leader( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match &self.expected_discover_leader_response { + Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), + Some(Err(status)) => Err(status.clone()), + None => Err(tonic::Status::unknown( + "No mock get_cluster_metadata response set", + )), + } + } +} + +#[tonic::async_trait] +impl RaftClientService for MockRpcService { + async fn handle_client_write( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match &self.expected_client_propose_response { + Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), + Some(Err(status)) => Err(status.clone()), + None => Err(tonic::Status::unknown( + "No mock handle_client_write response set", + )), + } + } + + async fn handle_client_read( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match &self.expected_client_read_response { + Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), + Some(Err(status)) => Err(status.clone()), + None => Err(tonic::Status::unknown( + "No mock handle_client_read response set", + )), + } + } +} diff --git a/d-engine-client/src/mock_rpc_service.rs b/d-engine-client/src/mock_rpc_service.rs new file mode 100644 index 00000000..edcc78fd --- /dev/null +++ b/d-engine-client/src/mock_rpc_service.rs @@ -0,0 +1,212 @@ +use d_engine_proto::client::ClientResponse; +use d_engine_proto::client::raft_client_service_server::RaftClientServiceServer; +use d_engine_proto::common::NodeRole; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::JoinResponse; +use d_engine_proto::server::cluster::NodeMeta; +use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementServiceServer; +use std::net::SocketAddr; +use std::sync::Arc; +use tokio::net::TcpListener; +use tokio::sync::oneshot; +use tonic::Status; +use tonic::codec::CompressionEncoding; +use tonic::transport::Channel; +use tonic_health::server::health_reporter; +use tracing::debug; +use tracing::info; + +use crate::mock_rpc::MockRpcService; + +pub struct MockNode; +impl MockNode { + pub async fn mock_listener( + mut mock_service: MockRpcService, + rx: oneshot::Receiver<()>, + is_ready: bool, + ) -> std::result::Result<(u16, SocketAddr), tonic::Status> { + // Return port + address + let (mut health_reporter, health_service) = health_reporter(); + if is_ready { + health_reporter.set_serving::>().await; + health_reporter.set_serving::>().await; + info!("set service is serving"); + } else { + health_reporter.set_not_serving::>().await; + health_reporter + .set_not_serving::>() + .await; + info!("set service is not serving"); + } + + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener + .local_addr() + .map_err(|e| tonic::Status::internal(format!("Failed to bind: {e}")))?; + let port = addr.port(); + debug!("starting mock rpc service:port={port}",); + + // Set the port in the service + mock_service.set_port(port); + + let mock_service = Arc::new(mock_service); + + let _r = tokio::spawn(async move { + tonic::transport::Server::builder() + .add_service(health_service) + .add_service( + RaftClientServiceServer::from_arc(mock_service.clone()) + .accept_compressed(CompressionEncoding::Gzip) + .send_compressed(CompressionEncoding::Gzip), + ) + .add_service( + ClusterManagementServiceServer::from_arc(mock_service.clone()) + .accept_compressed(CompressionEncoding::Gzip) + .send_compressed(CompressionEncoding::Gzip), + ) + // add as a dev-dependency the crate `tokio-stream` with feature `net` enabled + .serve_with_incoming_shutdown( + tokio_stream::wrappers::TcpListenerStream::new(listener), + async { + rx.await.ok(); + }, + ) + // .serve_with_shutdown("127.0.0.1:50051".parse().unwrap(), ) + .await + .unwrap(); + }); + + Ok((port, addr)) // Return both port and address + } + + // Update helper functions to handle dynamic ports + pub(crate) async fn mock_channel_with_port(port: u16) -> Channel { + Channel::from_shared(format!("http://127.0.0.1:{port}")) + .expect("valid address") + .connect() + .await + .expect("connection failed") + } + + #[allow(clippy::type_complexity)] + pub(crate) async fn simulate_mock_service_with_cluster_conf_reps( + rx: oneshot::Receiver<()>, + response_builder: Option< + Box std::result::Result + Send + Sync>, + >, + ) -> std::result::Result<(Channel, u16), tonic::Status> { + let builder = response_builder.unwrap_or_else(|| { + Box::new(|port: u16| { + Ok(ClusterMembership { + version: 1, + nodes: vec![NodeMeta { + id: 1, + role: NodeRole::Leader.into(), + address: format!("127.0.0.1:{port}",), + status: NodeStatus::Active.into(), + }], + }) + }) + }); + + let mock_service = MockRpcService::default().with_metadata_response(builder); + + let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; + let channel = Self::mock_channel_with_port(port).await; + Ok((channel, port)) + } + + #[allow(clippy::type_complexity)] + pub(crate) async fn simulate_client_read_mock_server( + rx: oneshot::Receiver<()>, + metadata_response_builder: Option< + Box std::result::Result + Send + Sync>, + >, + response: ClientResponse, + ) -> std::result::Result<(Channel, u16), tonic::Status> { + let builder = metadata_response_builder.unwrap_or_else(|| { + Box::new(|port: u16| { + Ok(ClusterMembership { + version: 1, + nodes: vec![NodeMeta { + id: 1, + role: NodeRole::Leader.into(), + address: format!("127.0.0.1:{port}",), + status: NodeStatus::Active.into(), + }], + }) + }) + }); + let mock_service = MockRpcService { + expected_metadata_response: Some(Arc::new(builder)), + expected_client_read_response: Some(Ok(response)), + ..Default::default() + }; + let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; + let channel = Self::mock_channel_with_port(port).await; + Ok((channel, port)) + } + + #[allow(clippy::type_complexity)] + pub(crate) async fn simulate_client_write_mock_server( + rx: oneshot::Receiver<()>, + metadata_response_builder: Option< + Box std::result::Result + Send + Sync>, + >, + response: ClientResponse, + ) -> std::result::Result<(Channel, u16), tonic::Status> { + let builder = metadata_response_builder.unwrap_or_else(|| { + Box::new(|port: u16| { + Ok(ClusterMembership { + version: 1, + nodes: vec![NodeMeta { + id: 1, + role: NodeRole::Leader.into(), + address: format!("127.0.0.1:{port}",), + status: NodeStatus::Active.into(), + }], + }) + }) + }); + let mock_service = MockRpcService { + expected_metadata_response: Some(Arc::new(builder)), + expected_client_propose_response: Some(Ok(response)), + ..Default::default() + }; + let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; + let channel = Self::mock_channel_with_port(port).await; + Ok((channel, port)) + } + + #[allow(clippy::type_complexity)] + pub async fn simulate_mock_service_with_join_cluster_reps( + rx: oneshot::Receiver<()>, + metadata_response_builder: Option< + Box std::result::Result + Send + Sync>, + >, + join_response: std::result::Result, + ) -> std::result::Result<(Channel, u16), tonic::Status> { + let builder = metadata_response_builder.unwrap_or_else(|| { + Box::new(|port: u16| { + Ok(ClusterMembership { + version: 1, + nodes: vec![NodeMeta { + id: 1, + role: NodeRole::Leader.into(), + address: format!("127.0.0.1:{port}",), + status: NodeStatus::Active.into(), + }], + }) + }) + }); + let mock_service = MockRpcService { + expected_metadata_response: Some(Arc::new(builder)), + expected_join_cluster_response: Some(join_response), + ..Default::default() + }; + let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; + let channel = Self::mock_channel_with_port(port).await; + Ok((channel, port)) + } +} diff --git a/d-engine-client/src/pool_test.rs b/d-engine-client/src/pool_test.rs index 07c05bb9..5e8aafbd 100644 --- a/d-engine-client/src/pool_test.rs +++ b/d-engine-client/src/pool_test.rs @@ -7,12 +7,12 @@ use tracing_test::traced_test; use crate::ClientConfig; use crate::ConnectionPool; +use crate::mock_rpc_service::MockNode; use crate::utils::get_now_as_u32; use d_engine_proto::common::NodeStatus; use d_engine_proto::error::ErrorCode; use d_engine_proto::server::cluster::ClusterMembership; use d_engine_proto::server::cluster::NodeMeta; -use d_engine_test_utils::MockNode; #[tokio::test] #[traced_test] diff --git a/d-engine-test-utils/Cargo.toml b/d-engine-test-utils/Cargo.toml deleted file mode 100644 index e65896a8..00000000 --- a/d-engine-test-utils/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "d-engine-test-utils" -version = "0.2.0" -edition = "2021" - -[dependencies] -d-engine-proto = { workspace = true } -tokio = { workspace = true } -tonic = { workspace = true } -tonic-health = { workspace = true } -tracing = { workspace = true } -bincode = { workspace = true } -tempfile = { workspace = true } -prost = { workspace = true } -async-compression = { workspace = true } -tokio-stream = { workspace = true } -tokio-tar = { workspace = true } -bytes = { workspace = true } -futures = { workspace = true } -http-body = { workspace = true } -http-body-util = { workspace = true } -crc32fast = { workspace = true } diff --git a/d-engine-test-utils/src/common.rs b/src/test_utils/common.rs similarity index 100% rename from d-engine-test-utils/src/common.rs rename to src/test_utils/common.rs diff --git a/d-engine-test-utils/src/entry_builder.rs b/src/test_utils/entry_builder.rs similarity index 100% rename from d-engine-test-utils/src/entry_builder.rs rename to src/test_utils/entry_builder.rs diff --git a/d-engine-test-utils/src/integration/mod.rs b/src/test_utils/integration/mod.rs similarity index 100% rename from d-engine-test-utils/src/integration/mod.rs rename to src/test_utils/integration/mod.rs diff --git a/d-engine-test-utils/src/integration/snapshot.rs b/src/test_utils/integration/snapshot.rs similarity index 100% rename from d-engine-test-utils/src/integration/snapshot.rs rename to src/test_utils/integration/snapshot.rs diff --git a/d-engine-test-utils/src/mock/mock_builder.rs b/src/test_utils/mock/mock_builder.rs similarity index 100% rename from d-engine-test-utils/src/mock/mock_builder.rs rename to src/test_utils/mock/mock_builder.rs diff --git a/d-engine-test-utils/src/mock/mock_rpc.rs b/src/test_utils/mock/mock_rpc.rs similarity index 100% rename from d-engine-test-utils/src/mock/mock_rpc.rs rename to src/test_utils/mock/mock_rpc.rs diff --git a/d-engine-test-utils/src/mock/mock_rpc_service.rs b/src/test_utils/mock/mock_rpc_service.rs similarity index 100% rename from d-engine-test-utils/src/mock/mock_rpc_service.rs rename to src/test_utils/mock/mock_rpc_service.rs diff --git a/d-engine-test-utils/src/mock/mock_storage_engine.rs b/src/test_utils/mock/mock_storage_engine.rs similarity index 100% rename from d-engine-test-utils/src/mock/mock_storage_engine.rs rename to src/test_utils/mock/mock_storage_engine.rs diff --git a/d-engine-test-utils/src/mock/mod.rs b/src/test_utils/mock/mod.rs similarity index 100% rename from d-engine-test-utils/src/mock/mod.rs rename to src/test_utils/mock/mod.rs diff --git a/d-engine-test-utils/src/mock_type_config.rs b/src/test_utils/mock_type_config.rs similarity index 100% rename from d-engine-test-utils/src/mock_type_config.rs rename to src/test_utils/mock_type_config.rs diff --git a/d-engine-test-utils/src/lib.rs b/src/test_utils/mod.rs similarity index 100% rename from d-engine-test-utils/src/lib.rs rename to src/test_utils/mod.rs From 8ea9c3fb050f4396854f9ff847aa1ca0d6bd9234 Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Mon, 20 Oct 2025 16:51:24 +0800 Subject: [PATCH 03/26] feat #167: start migrating d-engine-core --- Cargo.lock | 204 +++++++++++- Cargo.toml | 2 +- d-engine-core/Cargo.toml | 19 ++ d-engine-core/src/cluster_proto_extension.rs | 165 ++++++++++ .../commit_handler/default_commit_handler.rs | 15 +- .../default_commit_handler_test.rs | 92 +++--- .../src}/commit_handler/mod.rs | 0 .../src}/election/election_handler.rs | 107 +++---- .../src}/election/election_handler_test.rs | 50 +-- .../src}/election/mod.rs | 10 +- {src => d-engine-core/src}/errors.rs | 0 {src/core => d-engine-core/src}/event.rs | 44 +-- d-engine-core/src/lib.rs | 127 +++++++- d-engine-core/src/membership.rs | 193 +++++++++++ .../src}/purge/default_executor.rs | 4 +- .../src}/purge/default_executor_test.rs | 0 {src/core => d-engine-core/src}/purge/mod.rs | 2 +- {src/core => d-engine-core/src}/raft.rs | 14 +- .../src}/raft_context.rs | 0 .../src}/raft_oneshot_test.rs | 0 .../src}/raft_role/candidate_state.rs | 42 +-- .../src}/raft_role/candidate_state_test.rs | 88 ++--- .../src}/raft_role/follower_state.rs | 44 +-- .../src}/raft_role/follower_state_test.rs | 60 ++-- .../src}/raft_role/leader_state.rs | 93 +++--- .../src}/raft_role/leader_state_test.rs | 194 +++++------ .../src}/raft_role/learner_state.rs | 46 +-- .../src}/raft_role/learner_state_test.rs | 46 +-- .../src}/raft_role/mod.rs | 16 +- .../src}/raft_role/role_state.rs | 18 +- {src/core => d-engine-core/src}/raft_test.rs | 38 +-- .../src}/replication/batch_buffer.rs | 0 .../src}/replication/batch_buffer_test.rs | 0 .../src}/replication/mod.rs | 22 +- .../src}/replication/replication_handler.rs | 31 +- .../replication/replication_handler_test.rs | 174 +++++----- .../default_state_machine_handler.rs | 48 ++- .../default_state_machine_handler_test.rs | 58 ++-- .../src}/state_machine_handler/mod.rs | 14 +- .../snapshot_assembler.rs | 6 +- .../snapshot_assembler_test.rs | 4 +- .../state_machine_handler/snapshot_guard.rs | 0 .../snapshot_policy/composite.rs | 0 .../snapshot_policy/composite_test.rs | 10 +- .../snapshot_policy/log_size.rs | 0 .../snapshot_policy/log_size_test.rs | 26 +- .../snapshot_policy/mod.rs | 2 +- .../snapshot_policy/time_based.rs | 0 .../snapshot_policy/time_based_test.rs | 10 +- .../snapshot_stream_processor.rs | 0 .../storage/buffered/buffered_raft_log.rs | 0 .../buffered/buffered_raft_log_test.rs | 0 .../src}/storage/buffered/mod.rs | 0 d-engine-core/src/storage/mod.rs | 16 + .../src}/storage/raft_log.rs | 0 .../src}/storage/snapshot_path_manager.rs | 0 .../src}/storage/state_machine.rs | 0 .../src}/storage/state_machine_test.rs | 0 .../src}/storage/storage_engine.rs | 0 .../src}/storage/storage_engine_test.rs | 0 .../src}/storage/storage_test.rs | 0 .../src}/timer/election_timer.rs | 0 {src/core => d-engine-core/src}/timer/mod.rs | 0 .../src}/timer/replication_timer.rs | 0 {src => d-engine-core/src}/type_config.rs | 0 d-engine-core/src/utils/cluster.rs | 83 +++++ d-engine-core/src/utils/mod.rs | 4 + d-engine-core/src/utils/scoped_timer.rs | 23 ++ d-engine-docs/Cargo.toml | 6 + .../src}/docs/architecture/election-design.md | 0 .../error-handling-design-principles.md | 0 .../src}/docs/architecture/mod.rs | 0 .../new-node-join-architecture.md | 0 .../raft-log-persistence-architecture.md | 0 .../src}/docs/architecture/raft-role.md | 0 .../single-responsibility-principle.md | 0 .../architecture/snapshot-module-design.md | 0 .../src}/docs/client_guide/mod.rs | 0 .../docs/client_guide/read_consistency.md | 0 {src => d-engine-docs/src}/docs/mod.rs | 0 {src => d-engine-docs/src}/docs/overview.md | 0 .../src}/docs/performance/mod.rs | 0 .../throughput-optimization-guide.md | 0 .../src}/docs/quick-start-5min.md | 0 .../customize-raft-log-purge-executor.md | 0 .../server_guide/customize-state-machine.md | 0 .../server_guide/customize-storage-engine.md | 0 .../src}/docs/server_guide/mod.rs | 0 d-engine-docs/src/lib.rs | 1 + d-engine-proto/proto/common.proto | 1 + .../src/generated/d_engine.common.rs | 3 + d-engine-server/src/lib.rs | 1 + d-engine-server/src/main.rs | 3 - src/core/mod.rs | 103 ------ src/lib.rs | 3 - src/membership/mod.rs | 301 ------------------ 96 files changed, 1553 insertions(+), 1133 deletions(-) create mode 100644 d-engine-core/src/cluster_proto_extension.rs rename {src/core => d-engine-core/src}/commit_handler/default_commit_handler.rs (98%) rename {src/core => d-engine-core/src}/commit_handler/default_commit_handler_test.rs (95%) rename {src/core => d-engine-core/src}/commit_handler/mod.rs (100%) rename {src/core => d-engine-core/src}/election/election_handler.rs (84%) rename {src/core => d-engine-core/src}/election/election_handler_test.rs (95%) rename {src/core => d-engine-core/src}/election/mod.rs (96%) rename {src => d-engine-core/src}/errors.rs (100%) rename {src/core => d-engine-core/src}/event.rs (79%) create mode 100644 d-engine-core/src/membership.rs rename {src/core => d-engine-core/src}/purge/default_executor.rs (96%) rename {src/core => d-engine-core/src}/purge/default_executor_test.rs (100%) rename {src/core => d-engine-core/src}/purge/mod.rs (98%) rename {src/core => d-engine-core/src}/raft.rs (98%) rename {src/core => d-engine-core/src}/raft_context.rs (100%) rename {src/core => d-engine-core/src}/raft_oneshot_test.rs (100%) rename {src/core => d-engine-core/src}/raft_role/candidate_state.rs (96%) rename {src/core => d-engine-core/src}/raft_role/candidate_state_test.rs (95%) rename {src/core => d-engine-core/src}/raft_role/follower_state.rs (97%) rename {src/core => d-engine-core/src}/raft_role/follower_state_test.rs (98%) rename {src/core => d-engine-core/src}/raft_role/leader_state.rs (98%) rename {src/core => d-engine-core/src}/raft_role/leader_state_test.rs (97%) rename {src/core => d-engine-core/src}/raft_role/learner_state.rs (96%) rename {src/core => d-engine-core/src}/raft_role/learner_state_test.rs (97%) rename {src/core => d-engine-core/src}/raft_role/mod.rs (96%) rename {src/core => d-engine-core/src}/raft_role/role_state.rs (97%) rename {src/core => d-engine-core/src}/raft_test.rs (99%) rename {src/core => d-engine-core/src}/replication/batch_buffer.rs (100%) rename {src/core => d-engine-core/src}/replication/batch_buffer_test.rs (100%) rename {src/core => d-engine-core/src}/replication/mod.rs (95%) rename {src/core => d-engine-core/src}/replication/replication_handler.rs (97%) rename {src/core => d-engine-core/src}/replication/replication_handler_test.rs (97%) rename {src/core => d-engine-core/src}/state_machine_handler/default_state_machine_handler.rs (98%) rename {src/core => d-engine-core/src}/state_machine_handler/default_state_machine_handler_test.rs (98%) rename {src/core => d-engine-core/src}/state_machine_handler/mod.rs (95%) rename {src/core => d-engine-core/src}/state_machine_handler/snapshot_assembler.rs (99%) rename {src/core => d-engine-core/src}/state_machine_handler/snapshot_assembler_test.rs (98%) rename {src/core => d-engine-core/src}/state_machine_handler/snapshot_guard.rs (100%) rename {src/core => d-engine-core/src}/state_machine_handler/snapshot_policy/composite.rs (100%) rename {src/core => d-engine-core/src}/state_machine_handler/snapshot_policy/composite_test.rs (96%) rename {src/core => d-engine-core/src}/state_machine_handler/snapshot_policy/log_size.rs (100%) rename {src/core => d-engine-core/src}/state_machine_handler/snapshot_policy/log_size_test.rs (86%) rename {src/core => d-engine-core/src}/state_machine_handler/snapshot_policy/mod.rs (96%) rename {src/core => d-engine-core/src}/state_machine_handler/snapshot_policy/time_based.rs (100%) rename {src/core => d-engine-core/src}/state_machine_handler/snapshot_policy/time_based_test.rs (93%) rename {src/core => d-engine-core/src}/state_machine_handler/snapshot_stream_processor.rs (100%) rename {src => d-engine-core/src}/storage/buffered/buffered_raft_log.rs (100%) rename {src => d-engine-core/src}/storage/buffered/buffered_raft_log_test.rs (100%) rename {src => d-engine-core/src}/storage/buffered/mod.rs (100%) create mode 100644 d-engine-core/src/storage/mod.rs rename {src => d-engine-core/src}/storage/raft_log.rs (100%) rename {src => d-engine-core/src}/storage/snapshot_path_manager.rs (100%) rename {src => d-engine-core/src}/storage/state_machine.rs (100%) rename {src => d-engine-core/src}/storage/state_machine_test.rs (100%) rename {src => d-engine-core/src}/storage/storage_engine.rs (100%) rename {src => d-engine-core/src}/storage/storage_engine_test.rs (100%) rename {src => d-engine-core/src}/storage/storage_test.rs (100%) rename {src/core => d-engine-core/src}/timer/election_timer.rs (100%) rename {src/core => d-engine-core/src}/timer/mod.rs (100%) rename {src/core => d-engine-core/src}/timer/replication_timer.rs (100%) rename {src => d-engine-core/src}/type_config.rs (100%) create mode 100644 d-engine-core/src/utils/cluster.rs create mode 100644 d-engine-core/src/utils/mod.rs create mode 100644 d-engine-core/src/utils/scoped_timer.rs create mode 100644 d-engine-docs/Cargo.toml rename {src => d-engine-docs/src}/docs/architecture/election-design.md (100%) rename {src => d-engine-docs/src}/docs/architecture/error-handling-design-principles.md (100%) rename {src => d-engine-docs/src}/docs/architecture/mod.rs (100%) rename {src => d-engine-docs/src}/docs/architecture/new-node-join-architecture.md (100%) rename {src => d-engine-docs/src}/docs/architecture/raft-log-persistence-architecture.md (100%) rename {src => d-engine-docs/src}/docs/architecture/raft-role.md (100%) rename {src => d-engine-docs/src}/docs/architecture/single-responsibility-principle.md (100%) rename {src => d-engine-docs/src}/docs/architecture/snapshot-module-design.md (100%) rename {src => d-engine-docs/src}/docs/client_guide/mod.rs (100%) rename {src => d-engine-docs/src}/docs/client_guide/read_consistency.md (100%) rename {src => d-engine-docs/src}/docs/mod.rs (100%) rename {src => d-engine-docs/src}/docs/overview.md (100%) rename {src => d-engine-docs/src}/docs/performance/mod.rs (100%) rename {src => d-engine-docs/src}/docs/performance/throughput-optimization-guide.md (100%) rename {src => d-engine-docs/src}/docs/quick-start-5min.md (100%) rename {src => d-engine-docs/src}/docs/server_guide/customize-raft-log-purge-executor.md (100%) rename {src => d-engine-docs/src}/docs/server_guide/customize-state-machine.md (100%) rename {src => d-engine-docs/src}/docs/server_guide/customize-storage-engine.md (100%) rename {src => d-engine-docs/src}/docs/server_guide/mod.rs (100%) create mode 100644 d-engine-docs/src/lib.rs create mode 100644 d-engine-server/src/lib.rs delete mode 100644 d-engine-server/src/main.rs delete mode 100644 src/core/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 5681033c..b86161be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -237,6 +249,18 @@ dependencies = [ "libloading", ] +[[package]] +name = "config" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" +dependencies = [ + "nom", + "pathdiff", + "serde", + "toml", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -268,6 +292,26 @@ dependencies = [ [[package]] name = "d-engine-core" version = "0.1.0" +dependencies = [ + "bytes", + "config", + "d-engine-proto", + "dashmap", + "futures", + "metrics", + "mockall", + "nanoid", + "prost", + "serde", + "tokio", + "tonic", + "tracing", + "tracing-test", +] + +[[package]] +name = "d-engine-docs" +version = "0.2.0" [[package]] name = "d-engine-proto" @@ -289,6 +333,19 @@ dependencies = [ "rocksdb", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "deranged" version = "0.5.4" @@ -509,6 +566,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -743,6 +806,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.27" @@ -780,6 +852,16 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "metrics" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dea7ac8057892855ec285c440160265225438c3c45072613c25a4b26e98ef5" +dependencies = [ + "ahash", + "portable-atomic", +] + [[package]] name = "mime" version = "0.3.17" @@ -845,6 +927,15 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand", +] + [[package]] name = "nom" version = "7.1.3" @@ -894,6 +985,25 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "percent-encoding" version = "2.3.2" @@ -948,6 +1058,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + [[package]] name = "powerfmt" version = "0.2.0" @@ -1105,6 +1221,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.11.2" @@ -1233,6 +1358,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.219" @@ -1253,6 +1384,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1457,6 +1597,47 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.11.0", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tonic" version = "0.12.3" @@ -1687,6 +1868,12 @@ dependencies = [ "time", ] +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "want" version = "0.3.1" @@ -1717,6 +1904,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.52.0" @@ -1766,7 +1959,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -1873,6 +2066,15 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.45.0" diff --git a/Cargo.toml b/Cargo.toml index 8565e2c6..97696a84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [ "d-engine-proto", "d-engine-core", "d-engine-client", - "d-engine-server", + "d-engine-server", "d-engine-docs", ] resolver = "2" diff --git a/d-engine-core/Cargo.toml b/d-engine-core/Cargo.toml index 2996eb76..9d60e03f 100644 --- a/d-engine-core/Cargo.toml +++ b/d-engine-core/Cargo.toml @@ -4,3 +4,22 @@ version = "0.1.0" edition = "2024" [dependencies] +d-engine-proto = { workspace = true } + +tokio = { workspace = true } +tonic = { workspace = true } +bytes = { workspace = true } +tracing = { workspace = true } +futures = { workspace = true } +serde = { workspace = true } +prost = { workspace = true } + +nanoid = "0.4.0" +# using Dashset +dashmap = "5.5.3" +metrics = { version = "0.24", features = [] } +config = { version = "0.14.0", default-features = false, features = ["toml"] } + +[dev-dependencies] +mockall = "0.12.1" +tracing-test = "0.2" diff --git a/d-engine-core/src/cluster_proto_extension.rs b/d-engine-core/src/cluster_proto_extension.rs new file mode 100644 index 00000000..85c2e355 --- /dev/null +++ b/d-engine-core/src/cluster_proto_extension.rs @@ -0,0 +1,165 @@ +use d_engine_proto::{ + common::NodeStatus, error::ErrorCode, server::cluster::ClusterConfUpdateResponse, +}; + +pub trait ClusterConfUpdateResponseExt { + /// Generate a successful response (full success) + fn success( + node_id: u32, + term: u64, + version: u64, + ) -> Self; + + /// Generate a failed response (Stale leader term) + fn higher_term( + node_id: u32, + term: u64, + version: u64, + ) -> Self; + + /// Generate a failed response (Request sent to non-leader or an out-dated leader) + fn not_leader( + node_id: u32, + term: u64, + version: u64, + ) -> Self; + + /// Generate a failed response (Stale configuration version) + fn version_conflict( + node_id: u32, + term: u64, + version: u64, + ) -> Self; + + /// Generate a failed response (Malformed change request) + #[allow(unused)] + fn invalid_change( + node_id: u32, + term: u64, + version: u64, + ) -> Self; + + /// Generate a failed response (Server-side processing error) + fn internal_error( + node_id: u32, + term: u64, + version: u64, + ) -> Self; + + #[allow(unused)] + fn is_higher_term(&self) -> bool; +} + +pub trait NodeStatusExt { + fn is_promotable(&self) -> bool; + + fn is_i32_promotable(value: i32) -> bool; +} + +impl ClusterConfUpdateResponseExt for ClusterConfUpdateResponse { + /// Generate a successful response (full success) + fn success( + node_id: u32, + term: u64, + version: u64, + ) -> Self { + Self { + id: node_id, + term, + version, + success: true, + error_code: ErrorCode::None.into(), + } + } + + /// Generate a failed response (Stale leader term) + fn higher_term( + node_id: u32, + term: u64, + version: u64, + ) -> Self { + Self { + id: node_id, + term, + version, + success: false, + error_code: ErrorCode::TermOutdated.into(), + } + } + + /// Generate a failed response (Request sent to non-leader or an out-dated leader) + fn not_leader( + node_id: u32, + term: u64, + version: u64, + ) -> Self { + Self { + id: node_id, + term, + version, + success: false, + error_code: ErrorCode::NotLeader.into(), + } + } + + /// Generate a failed response (Stale configuration version) + fn version_conflict( + node_id: u32, + term: u64, + version: u64, + ) -> Self { + Self { + id: node_id, + term, + version, + success: false, + error_code: ErrorCode::VersionConflict.into(), + } + } + + /// Generate a failed response (Malformed change request) + #[allow(unused)] + fn invalid_change( + node_id: u32, + term: u64, + version: u64, + ) -> Self { + Self { + id: node_id, + term, + version, + success: false, + error_code: ErrorCode::InvalidChange.into(), + } + } + + /// Generate a failed response (Server-side processing error) + fn internal_error( + node_id: u32, + term: u64, + version: u64, + ) -> Self { + Self { + id: node_id, + term, + version, + success: false, + error_code: ErrorCode::InternalError.into(), + } + } + + #[allow(unused)] + fn is_higher_term(&self) -> bool { + self.error_code == >::into(ErrorCode::TermOutdated) + } +} + +impl NodeStatusExt for NodeStatus { + fn is_promotable(&self) -> bool { + matches!(self, NodeStatus::Syncing) + } + + fn is_i32_promotable(value: i32) -> bool { + matches!(value, v if v == (NodeStatus::Syncing as i32)) + } +} diff --git a/src/core/commit_handler/default_commit_handler.rs b/d-engine-core/src/commit_handler/default_commit_handler.rs similarity index 98% rename from src/core/commit_handler/default_commit_handler.rs rename to d-engine-core/src/commit_handler/default_commit_handler.rs index cf0a4628..187b70eb 100644 --- a/src/core/commit_handler/default_commit_handler.rs +++ b/d-engine-core/src/commit_handler/default_commit_handler.rs @@ -11,20 +11,20 @@ use tracing::trace; use tracing::warn; use super::CommitHandler; -use crate::alias::MOF; -use crate::alias::ROF; -use crate::alias::SMHOF; -use crate::proto::common::entry_payload::Payload; -use crate::proto::common::Entry; -use crate::scoped_timer::ScopedTimer; use crate::Membership; use crate::NewCommitData; use crate::RaftEvent; use crate::RaftLog; use crate::RaftNodeConfig; use crate::Result; +use crate::ScopedTimer; use crate::StateMachineHandler; use crate::TypeConfig; +use crate::alias::MOF; +use crate::alias::ROF; +use crate::alias::SMHOF; +use d_engine_proto::common::Entry; +use d_engine_proto::common::entry_payload::Payload; // Dependencies container pub struct CommitHandlerDependencies { @@ -277,8 +277,7 @@ where let entries = std::mem::take(batch); trace!( "[Node-{}] Flushing command batch: {:?}", - self.my_id, - entries + self.my_id, entries ); self.state_machine_handler.apply_chunk(entries).await?; } diff --git a/src/core/commit_handler/default_commit_handler_test.rs b/d-engine-core/src/commit_handler/default_commit_handler_test.rs similarity index 95% rename from src/core/commit_handler/default_commit_handler_test.rs rename to d-engine-core/src/commit_handler/default_commit_handler_test.rs index d8e4a313..c3495570 100644 --- a/src/core/commit_handler/default_commit_handler_test.rs +++ b/d-engine-core/src/commit_handler/default_commit_handler_test.rs @@ -12,13 +12,6 @@ use tokio::time::{self}; use super::CommitHandler; use super::CommitHandlerDependencies; use super::DefaultCommitHandler; -use crate::proto::common::membership_change::Change; -use crate::proto::common::Entry; -use crate::proto::common::EntryPayload; -use crate::proto::common::LogId; -use crate::proto::storage::SnapshotMetadata; -use crate::test_utils::generate_insert_commands; -use crate::test_utils::MockTypeConfig; use crate::CommitHandlerConfig; use crate::Error; use crate::MockMembership; @@ -29,7 +22,14 @@ use crate::RaftConfig; use crate::RaftEvent; use crate::RaftNodeConfig; use crate::Result; -use crate::LEADER; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::generate_insert_commands; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Leader; +use d_engine_proto::common::membership_change::Change; +use d_engine_proto::server::storage::SnapshotMetadata; const TEST_TERM: u64 = 1; @@ -313,7 +313,7 @@ fn setup( }, ..Default::default() }; - DefaultCommitHandler::::new(1, LEADER, 1, deps, Arc::new(config), new_commit_rx) + DefaultCommitHandler::::new(1, Leader, 1, deps, Arc::new(config), new_commit_rx) } /// # Case 1: interval_uses_correct_duration @@ -403,11 +403,11 @@ mod run_test { use tokio::time; use super::*; - use crate::proto::common::membership_change::Change; - use crate::proto::common::AddNode; - use crate::proto::common::RemoveNode; - use crate::FOLLOWER; - use crate::LEADER; + use d_engine_proto::common::AddNode; + use d_engine_proto::common::NodeRole::Follower; + use d_engine_proto::common::NodeRole::Leader; + use d_engine_proto::common::RemoveNode; + use d_engine_proto::common::membership_change::Change; /// 1. Test happy path with all entry types #[tokio::test] @@ -427,7 +427,7 @@ mod run_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -441,7 +441,7 @@ mod run_test { // Send commits to trigger processing for i in 1..=4 { - harness.send_commit(i, LEADER).await; + harness.send_commit(i, Leader).await; } // Verify snapshot triggered @@ -464,7 +464,7 @@ mod run_test { ); let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -476,8 +476,8 @@ mod run_test { ); harness.run_handler().await; - harness.send_commit(1, LEADER).await; - harness.send_commit(2, FOLLOWER).await; + harness.send_commit(1, Leader).await; + harness.send_commit(2, Follower).await; // Should not process second command time::sleep(Duration::from_millis(50)).await; @@ -497,7 +497,7 @@ mod run_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -509,7 +509,7 @@ mod run_test { ); harness.run_handler().await; - harness.send_commit(1, LEADER).await; + harness.send_commit(1, Leader).await; time::sleep(Duration::from_millis(50)).await; harness.shutdown_tx.send(()).unwrap(); harness.handle.unwrap().await.unwrap(); @@ -526,7 +526,7 @@ mod run_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -540,7 +540,7 @@ mod run_test { // Send all commits at once for i in 1..=1000 { - harness.send_commit(i, LEADER).await; + harness.send_commit(i, Leader).await; } println!("Sent all commits"); @@ -576,7 +576,7 @@ mod run_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -590,7 +590,7 @@ mod run_test { // Send commits to trigger processing for i in 1..=2 { - harness.send_commit(i, LEADER).await; + harness.send_commit(i, Leader).await; } tokio::time::advance(Duration::from_millis(3)).await; // Clean shutdown @@ -629,7 +629,7 @@ mod run_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -642,7 +642,7 @@ mod run_test { harness.run_handler().await; for i in 1..=batch_thresold { - harness.send_commit(i, LEADER).await; + harness.send_commit(i, Leader).await; } tokio::time::advance(Duration::from_millis(2)).await; tokio::time::sleep(Duration::from_millis(2)).await; @@ -682,7 +682,7 @@ mod run_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -695,7 +695,7 @@ mod run_test { harness.run_handler().await; for i in 1..=(batch_thresold - 10) { - harness.send_commit(i, LEADER).await; + harness.send_commit(i, Leader).await; } tokio::time::advance(Duration::from_millis(2)).await; tokio::time::sleep(Duration::from_millis(2)).await; @@ -735,7 +735,7 @@ mod run_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -748,7 +748,7 @@ mod run_test { harness.run_handler().await; for i in 1..=batch_thresold { - harness.send_commit(i, LEADER).await; + harness.send_commit(i, Leader).await; } tokio::time::advance(Duration::from_millis(2)).await; tokio::time::sleep(Duration::from_millis(2)).await; @@ -764,10 +764,10 @@ mod process_batch_test { use parking_lot::Mutex; use super::*; - use crate::proto::common::membership_change::Change; - use crate::proto::common::AddNode; - use crate::proto::common::RemoveNode; use crate::test_utils::*; + use d_engine_proto::common::AddNode; + use d_engine_proto::common::RemoveNode; + use d_engine_proto::common::membership_change::Change; // Test helper setup with configurable mocks // fn setup_test_handler( @@ -841,7 +841,7 @@ mod process_batch_test { async fn processes_empty_batch_successfully() { let last_applied = 0; let mut harness = setup_harness( - LEADER, + Leader, 1, vec![], last_applied as u64, @@ -868,7 +868,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -900,7 +900,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -940,7 +940,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -971,7 +971,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -999,7 +999,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -1025,7 +1025,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -1047,7 +1047,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -1082,7 +1082,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -1108,7 +1108,7 @@ mod process_batch_test { let entries = build_entries(r, 1); let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -1142,7 +1142,7 @@ mod process_batch_test { let order_capture = process_order.clone(); let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, @@ -1191,7 +1191,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - LEADER, + Leader, 1, entries, last_applied as u64, diff --git a/src/core/commit_handler/mod.rs b/d-engine-core/src/commit_handler/mod.rs similarity index 100% rename from src/core/commit_handler/mod.rs rename to d-engine-core/src/commit_handler/mod.rs diff --git a/src/core/election/election_handler.rs b/d-engine-core/src/election/election_handler.rs similarity index 84% rename from src/core/election/election_handler.rs rename to d-engine-core/src/election/election_handler.rs index e2b6b085..c60303a8 100644 --- a/src/core/election/election_handler.rs +++ b/d-engine-core/src/election/election_handler.rs @@ -9,15 +9,6 @@ use tracing::trace; use tracing::warn; use super::ElectionCore; -use crate::alias::MOF; -use crate::alias::ROF; -use crate::alias::TROF; -use crate::cluster::is_majority; -use crate::if_higher_term_found; -use crate::is_target_log_more_recent; -use crate::proto::common::LogId; -use crate::proto::election::VoteRequest; -use crate::proto::election::VotedFor; use crate::ElectionError; use crate::Membership; use crate::RaftLog; @@ -26,6 +17,15 @@ use crate::Result; use crate::StateUpdate; use crate::Transport; use crate::TypeConfig; +use crate::alias::MOF; +use crate::alias::ROF; +use crate::alias::TROF; +use crate::cluster::is_majority; +use crate::if_higher_term_found; +use crate::is_target_log_more_recent; +use d_engine_proto::common::LogId; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::election::VotedFor; #[derive(Clone)] pub struct ElectionHandler { @@ -188,60 +188,55 @@ where }; // Check if we should grant the vote - let grant_vote = - if request.term < current_term { - // Request term is lower, cannot grant vote - trace!( + let grant_vote = if request.term < current_term { + // Request term is lower, cannot grant vote + trace!( "[node-{} -> node-{}] Request term is lower, cannot grant vote. VoteRequest = {:?}", request.candidate_id, self.my_id, &request ); - false - } else { - // Request term is >= current term - // Check log completeness - if !is_target_log_more_recent( - last_logid.index, - last_logid.term, + false + } else { + // Request term is >= current term + // Check log completeness + if !is_target_log_more_recent( + last_logid.index, + last_logid.term, + request.last_log_index, + request.last_log_term, + ) { + trace!( + "node-{}: last_log_index({}(t:{})) -> node-{}: last_log_index({}(t:{}))", + request.candidate_id, request.last_log_index, request.last_log_term, - ) { + self.my_id, + last_logid.index, + last_logid.term + ); + + false + } else { + // Check if already voted for someone else in this term + if let Some(voted_for) = new_voted_for_option { trace!( - "node-{}: last_log_index({}(t:{})) -> node-{}: last_log_index({}(t:{}))", - request.candidate_id, - request.last_log_index, - request.last_log_term, - self.my_id, - last_logid.index, - last_logid.term + "[node-{} -> node-{}] node-{} current vote: {:?}", + request.candidate_id, self.my_id, self.my_id, &voted_for ); - - false + // If already voted for someone else, cannot grant vote unless it's the same + // candidate + voted_for.voted_for_term == request.term + && voted_for.voted_for_id == request.candidate_id } else { - // Check if already voted for someone else in this term - if let Some(voted_for) = new_voted_for_option { - trace!( - "[node-{} -> node-{}] node-{} current vote: {:?}", - request.candidate_id, - self.my_id, - self.my_id, - &voted_for - ); - // If already voted for someone else, cannot grant vote unless it's the same - // candidate - voted_for.voted_for_term == request.term - && voted_for.voted_for_id == request.candidate_id - } else { - trace!( - "node-{} vote for node-{} successfully!", - self.my_id, - request.candidate_id, - ); - - true - } + trace!( + "node-{} vote for node-{} successfully!", + self.my_id, request.candidate_id, + ); + + true } - }; + } + }; if grant_vote { new_voted_for = Some(VotedFor { @@ -250,14 +245,12 @@ where }); trace!( "node-{} -> node-{} successfully!", - request.candidate_id, - self.my_id, + request.candidate_id, self.my_id, ); } else { trace!( "node-{} -> node-{} failed!", - request.candidate_id, - self.my_id, + request.candidate_id, self.my_id, ); } diff --git a/src/core/election/election_handler_test.rs b/d-engine-core/src/election/election_handler_test.rs similarity index 95% rename from src/core/election/election_handler_test.rs rename to d-engine-core/src/election/election_handler_test.rs index f4e6eb18..6b4706b8 100644 --- a/src/core/election/election_handler_test.rs +++ b/d-engine-core/src/election/election_handler_test.rs @@ -4,17 +4,6 @@ use tokio::sync::watch; use tracing::debug; use tracing_test::traced_test; -use crate::alias::ROF; -use crate::alias::TROF; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::LogId; -use crate::proto::common::NodeStatus; -use crate::proto::election::VoteRequest; -use crate::proto::election::VoteResponse; -use crate::proto::election::VotedFor; -use crate::test_utils::mock_membership; -use crate::test_utils::setup_raft_components; -use crate::test_utils::MockTypeConfig; use crate::ConsensusError; use crate::ElectionCore; use crate::ElectionError; @@ -24,7 +13,18 @@ use crate::MockMembership; use crate::MockRaftLog; use crate::MockTransport; use crate::VoteResult; -use crate::FOLLOWER; +use crate::alias::ROF; +use crate::alias::TROF; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::mock_membership; +use crate::test_utils::setup_raft_components; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::server::cluster::NodeMeta; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::election::VotedFor; struct TestConext { election_handler: ElectionHandler, @@ -183,7 +183,7 @@ async fn test_broadcast_vote_requests_case3() { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -311,17 +311,19 @@ async fn test_handle_vote_request_case1() { last_log_term, }; let voted_for_option = None; - assert!(test_context - .election_handler - .handle_vote_request( - vote_request, - current_term, - voted_for_option, - &Arc::new(test_context.raft_log_mock), - ) - .await - .is_ok_and(move |state_update| state_update.new_voted_for.is_some() - && state_update.term_update.unwrap() == request_term)); + assert!( + test_context + .election_handler + .handle_vote_request( + vote_request, + current_term, + voted_for_option, + &Arc::new(test_context.raft_log_mock), + ) + .await + .is_ok_and(move |state_update| state_update.new_voted_for.is_some() + && state_update.term_update.unwrap() == request_term) + ); } /// # Case 2: Test if vote request is illegal diff --git a/src/core/election/mod.rs b/d-engine-core/src/election/mod.rs similarity index 96% rename from src/core/election/mod.rs rename to d-engine-core/src/election/mod.rs index a23e1e2b..9f48cf08 100644 --- a/src/core/election/mod.rs +++ b/d-engine-core/src/election/mod.rs @@ -18,14 +18,14 @@ use std::sync::Arc; use mockall::automock; use tonic::async_trait; -use crate::alias::MOF; -use crate::alias::ROF; -use crate::alias::TROF; -use crate::proto::election::VoteRequest; -use crate::proto::election::VotedFor; use crate::RaftNodeConfig; use crate::Result; use crate::TypeConfig; +use crate::alias::MOF; +use crate::alias::ROF; +use crate::alias::TROF; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::election::VotedFor; /// State transition data for election outcomes #[derive(Debug)] diff --git a/src/errors.rs b/d-engine-core/src/errors.rs similarity index 100% rename from src/errors.rs rename to d-engine-core/src/errors.rs diff --git a/src/core/event.rs b/d-engine-core/src/event.rs similarity index 79% rename from src/core/event.rs rename to d-engine-core/src/event.rs index 65b4198e..832fcf14 100644 --- a/src/core/event.rs +++ b/d-engine-core/src/event.rs @@ -2,31 +2,31 @@ use std::path::PathBuf; use tonic::Status; -use crate::proto::client::ClientReadRequest; -use crate::proto::client::ClientResponse; -use crate::proto::client::ClientWriteRequest; -use crate::proto::cluster::ClusterConfChangeRequest; -use crate::proto::cluster::ClusterConfUpdateResponse; -use crate::proto::cluster::ClusterMembership; -use crate::proto::cluster::JoinRequest; -use crate::proto::cluster::JoinResponse; -use crate::proto::cluster::LeaderDiscoveryRequest; -use crate::proto::cluster::LeaderDiscoveryResponse; -use crate::proto::cluster::MetadataRequest; -use crate::proto::common::LogId; -use crate::proto::election::VoteRequest; -use crate::proto::election::VoteResponse; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::replication::AppendEntriesResponse; -use crate::proto::storage::PurgeLogRequest; -use crate::proto::storage::PurgeLogResponse; -use crate::proto::storage::SnapshotAck; -use crate::proto::storage::SnapshotChunk; -use crate::proto::storage::SnapshotMetadata; -use crate::proto::storage::SnapshotResponse; use crate::MaybeCloneOneshotSender; use crate::Result; use crate::StreamResponseSender; +use d_engine_proto::client::ClientReadRequest; +use d_engine_proto::client::ClientResponse; +use d_engine_proto::client::ClientWriteRequest; +use d_engine_proto::common::LogId; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::JoinRequest; +use d_engine_proto::server::cluster::JoinResponse; +use d_engine_proto::server::cluster::LeaderDiscoveryRequest; +use d_engine_proto::server::cluster::LeaderDiscoveryResponse; +use d_engine_proto::server::cluster::MetadataRequest; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::storage::PurgeLogRequest; +use d_engine_proto::server::storage::PurgeLogResponse; +use d_engine_proto::server::storage::SnapshotAck; +use d_engine_proto::server::storage::SnapshotChunk; +use d_engine_proto::server::storage::SnapshotMetadata; +use d_engine_proto::server::storage::SnapshotResponse; #[derive(Debug, Clone)] pub(crate) struct NewCommitData { diff --git a/d-engine-core/src/lib.rs b/d-engine-core/src/lib.rs index 18963301..60149a5e 100644 --- a/d-engine-core/src/lib.rs +++ b/d-engine-core/src/lib.rs @@ -1,17 +1,120 @@ -pub fn add( - left: u64, - right: u64, -) -> u64 { - left + right -} +mod cluster_proto_extension; +mod commit_handler; +mod election; +mod errors; +mod event; +mod membership; +mod purge; +mod raft; +mod raft_context; +mod raft_role; +mod replication; +mod state_machine_handler; +mod timer; +mod type_config; +mod utils; + +pub mod storage; +pub use membership::*; + +pub(crate) use cluster_proto_extension::*; +pub(crate) use commit_handler::*; +pub(crate) use election::*; +pub(crate) use errors::*; +pub(crate) use event::*; +pub(crate) use purge::*; +pub(crate) use raft::*; +pub(crate) use raft_context::*; +pub use storage::*; + +#[doc(hidden)] +pub use raft_role::*; +#[doc(hidden)] +pub use utils::*; + +pub(crate) use replication::*; +pub(crate) use state_machine_handler::*; +pub(crate) use timer::*; +#[doc(hidden)] +pub use type_config::*; #[cfg(test)] -mod tests { - use super::*; +mod raft_oneshot_test; +#[cfg(test)] +mod raft_test; + +use bytes::Bytes; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::MembershipChange; +use d_engine_proto::common::Noop; +use d_engine_proto::common::entry_payload::Payload; +use d_engine_proto::common::membership_change::Change; +use tracing::instrument; + +/// In raft, during any Leader to Peer communication, +/// if received response term is bigger than Leader's, +/// the current Leader need downgrade to follower +/// and update its term to higher one. +/// +/// e.g. Append Entries RPC +/// e.g. Election: receive VoteResponse +/// e.g. Sync cluster membership configure +/// @return: true - found higher term; +pub(crate) fn if_higher_term_found( + my_current_term: u64, + term: u64, + is_learner: bool, +) -> bool { + //means I am fake leader or we should ignore learner's response + if !is_learner && my_current_term < term { + tracing::warn!("my_current_term: {} < term: {} ?", my_current_term, term); + return true; + } + + false +} + +/// Raft paper: 5.4.1 Election restriction +/// +/// Raft determines which of two logs is more up-to-date by comparing the index and term of the last +/// entries in the logs. If the logs have last entries with different terms, then the log with the +/// later term is more up-to-date. If the logs end with the same term, then whichever log is longer +/// is more up-to-date. +#[instrument] +pub(crate) fn is_target_log_more_recent( + my_last_log_index: u64, + my_last_log_term: u64, + target_last_log_index: u64, + target_last_log_term: u64, +) -> bool { + (target_last_log_term > my_last_log_term) + || (target_last_log_term == my_last_log_term && target_last_log_index >= my_last_log_index) +} + +#[derive(Debug, Clone, Copy)] +pub enum QuorumStatus { + Confirmed, // Confirmed by the majority of nodes + LostQuorum, // Unable to obtain majority + NetworkError, // Network problem (can be retried) +} + +impl EntryPayload { + pub fn command(command: Bytes) -> Self { + Self { + payload: Some(Payload::Command(command)), + } + } + pub fn noop() -> Self { + Self { + payload: Some(Payload::Noop(Noop {})), + } + } - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + pub fn config(change: Change) -> Self { + Self { + payload: Some(Payload::Config(MembershipChange { + change: Some(change), + })), + } } } diff --git a/d-engine-core/src/membership.rs b/d-engine-core/src/membership.rs new file mode 100644 index 00000000..917a200b --- /dev/null +++ b/d-engine-core/src/membership.rs @@ -0,0 +1,193 @@ +#[cfg(test)] +use mockall::automock; +use tonic::async_trait; +use tonic::transport::Channel; + +use crate::Result; +use crate::TypeConfig; +use d_engine_proto::common::MembershipChange; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::NodeMeta; + +// Add connection type management in RpcPeerChannels +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub(crate) enum ConnectionType { + Control, // Used for key operations such as elections/heartbeats + Data, // Used for log replication + Bulk, // Used for high-traffic operations such as snapshot transmission +} +impl ConnectionType { + pub(crate) fn all() -> Vec { + vec![ + ConnectionType::Control, + ConnectionType::Data, + ConnectionType::Bulk, + ] + } +} + +#[cfg_attr(test, automock)] +#[async_trait] +pub trait Membership: Sync + Send + 'static +where + T: TypeConfig, +{ + /// All nodes (including itself) + #[allow(unused)] + async fn members(&self) -> Vec; + + /// All non-self nodes (including Syncing and Active) + /// Note: + /// Joining node has not start its Raft event processing engine yet. + async fn replication_peers(&self) -> Vec; + + /// All non-self nodes in Active state + async fn voters(&self) -> Vec; + + /// All pending active nodes in Active state + #[allow(unused)] + async fn nodes_with_status( + &self, + status: NodeStatus, + ) -> Vec; + + async fn get_node_status( + &self, + node_id: u32, + ) -> Option; + + async fn check_cluster_is_ready(&self) -> Result<()>; + + async fn get_peers_id_with_condition( + &self, + condition: F, + ) -> Vec + where + F: Fn(i32) -> bool + Send + Sync + 'static; + + async fn mark_leader_id( + &self, + leader_id: u32, + ) -> Result<()>; + + async fn current_leader_id(&self) -> Option; + + /// Reset old leader to follower + async fn reset_leader(&self) -> Result<()>; + + /// If node role not found return Error + async fn update_node_role( + &self, + node_id: u32, + new_role: i32, + ) -> Result<()>; + + /// retrieve latest cluster membership + async fn retrieve_cluster_membership_config(&self) -> ClusterMembership; + + /// invoked when receive requests from Leader + async fn update_cluster_conf_from_leader( + &self, + my_id: u32, + my_current_term: u64, + current_conf_version: u64, + current_leader_id: Option, + cluster_conf_change_req: &ClusterConfChangeRequest, + ) -> Result; + + async fn get_cluster_conf_version(&self) -> u64; + + async fn update_conf_version( + &self, + version: u64, + ); + + #[allow(unused)] + async fn incr_conf_version(&self); + + /// Add a new node as a learner + async fn add_learner( + &self, + node_id: u32, + address: String, + ) -> Result<()>; + + /// Activate node + #[allow(unused)] + async fn activate_node( + &mut self, + new_node_id: u32, + ) -> Result<()>; + + /// Update status of a node + async fn update_node_status( + &self, + node_id: u32, + status: NodeStatus, + ) -> Result<()>; + + /// Check if the node already exists + async fn contains_node( + &self, + node_id: u32, + ) -> bool; + + async fn retrieve_node_meta( + &self, + node_id: u32, + ) -> Option; + + /// Elegantly remove nodes + async fn remove_node( + &self, + node_id: u32, + ) -> Result<()>; + + /// Forcefully remove faulty nodes + #[allow(unused)] + async fn force_remove_node( + &self, + node_id: u32, + ) -> Result<()>; + + /// Get all node status + #[allow(unused)] + async fn get_all_nodes(&self) -> Vec; + + /// Pre-warms connection cache for all replication peers + async fn pre_warm_connections(&self) -> Result<()>; + + async fn get_peer_channel( + &self, + node_id: u32, + conn_type: ConnectionType, + ) -> Option; + + async fn get_address( + &self, + node_id: u32, + ) -> Option; + + /// Apply committed configuration change + async fn apply_config_change( + &self, + change: MembershipChange, + ) -> Result<()>; + + async fn notify_config_applied( + &self, + index: u64, + ); + + async fn get_zombie_candidates(&self) -> Vec; + + /// If new node could rejoin the cluster again + async fn can_rejoin( + &self, + node_id: u32, + role: i32, + ) -> Result<()>; +} diff --git a/src/core/purge/default_executor.rs b/d-engine-core/src/purge/default_executor.rs similarity index 96% rename from src/core/purge/default_executor.rs rename to d-engine-core/src/purge/default_executor.rs index 4d45889a..5f52c4f3 100644 --- a/src/core/purge/default_executor.rs +++ b/d-engine-core/src/purge/default_executor.rs @@ -2,12 +2,12 @@ use std::sync::Arc; use tonic::async_trait; -use crate::alias::ROF; -use crate::proto::common::LogId; use crate::PurgeExecutor; use crate::RaftLog; use crate::Result; use crate::TypeConfig; +use crate::alias::ROF; +use d_engine_proto::common::LogId; /// Default implementation using the configured log storage pub struct DefaultPurgeExecutor { diff --git a/src/core/purge/default_executor_test.rs b/d-engine-core/src/purge/default_executor_test.rs similarity index 100% rename from src/core/purge/default_executor_test.rs rename to d-engine-core/src/purge/default_executor_test.rs diff --git a/src/core/purge/mod.rs b/d-engine-core/src/purge/mod.rs similarity index 98% rename from src/core/purge/mod.rs rename to d-engine-core/src/purge/mod.rs index 62dbb409..72aa0e79 100644 --- a/src/core/purge/mod.rs +++ b/d-engine-core/src/purge/mod.rs @@ -15,8 +15,8 @@ mod default_executor_test; use mockall::automock; use tonic::async_trait; -use crate::proto::common::LogId; use crate::Result; +use d_engine_proto::common::LogId; /// Defines the behavior for log entry compaction and physical deletion. /// diff --git a/src/core/raft.rs b/d-engine-core/src/raft.rs similarity index 98% rename from src/core/raft.rs rename to d-engine-core/src/raft.rs index bc3fe41e..42e29aea 100644 --- a/src/core/raft.rs +++ b/d-engine-core/src/raft.rs @@ -9,8 +9,6 @@ use tracing::info; use tracing::trace; use tracing::warn; -#[cfg(test)] -use super::raft_event_to_test_event; use super::NewCommitData; use super::RaftContext; use super::RaftCoreHandlers; @@ -18,15 +16,17 @@ use super::RaftEvent; use super::RaftRole; use super::RaftStorageHandles; use super::RoleEvent; -use crate::alias::MOF; -use crate::alias::TROF; -use crate::proto::common::EntryPayload; +#[cfg(test)] +use super::raft_event_to_test_event; use crate::Membership; use crate::NetworkError; use crate::RaftLog; use crate::RaftNodeConfig; use crate::Result; use crate::TypeConfig; +use crate::alias::MOF; +use crate::alias::TROF; +use d_engine_proto::common::EntryPayload; pub struct Raft where @@ -272,7 +272,9 @@ where .await .unwrap_or(false) { - warn!("Verify leadership in new term failed. Now the node is going to step back to Follower..."); + warn!( + "Verify leadership in new term failed. Now the node is going to step back to Follower..." + ); self.role_tx.send(RoleEvent::BecomeFollower(None)).map_err(|e| { let error_str = format!("{e:?}"); error!("Failed to send: {}", error_str); diff --git a/src/core/raft_context.rs b/d-engine-core/src/raft_context.rs similarity index 100% rename from src/core/raft_context.rs rename to d-engine-core/src/raft_context.rs diff --git a/src/core/raft_oneshot_test.rs b/d-engine-core/src/raft_oneshot_test.rs similarity index 100% rename from src/core/raft_oneshot_test.rs rename to d-engine-core/src/raft_oneshot_test.rs diff --git a/src/core/raft_role/candidate_state.rs b/d-engine-core/src/raft_role/candidate_state.rs similarity index 96% rename from src/core/raft_role/candidate_state.rs rename to d-engine-core/src/raft_role/candidate_state.rs index fb804888..ba1c6341 100644 --- a/src/core/raft_role/candidate_state.rs +++ b/d-engine-core/src/raft_role/candidate_state.rs @@ -4,28 +4,21 @@ use std::sync::Arc; use tokio::sync::mpsc; use tokio::time::Instant; -use tonic::async_trait; use tonic::Status; +use tonic::async_trait; use tracing::debug; use tracing::error; use tracing::info; use tracing::trace; use tracing::warn; -use super::can_serve_read_locally; -use super::follower_state::FollowerState; -use super::role_state::RaftRoleState; use super::RaftRole; use super::Result; use super::SharedState; use super::StateSnapshot; -use super::CANDIDATE; -use crate::proto::client::ClientResponse; -use crate::proto::cluster::ClusterConfUpdateResponse; -use crate::proto::common::LogId; -use crate::proto::election::VoteResponse; -use crate::proto::election::VotedFor; -use crate::proto::error::ErrorCode; +use super::can_serve_read_locally; +use super::follower_state::FollowerState; +use super::role_state::RaftRoleState; use crate::ConsensusError; use crate::ElectionCore; use crate::ElectionError; @@ -42,6 +35,13 @@ use crate::RoleEvent; use crate::StateMachineHandler; use crate::StateTransitionError; use crate::TypeConfig; +use d_engine_proto::client::ClientResponse; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Candidate; +use d_engine_proto::error::ErrorCode; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::election::VotedFor; /// Candidate node's volatile state during leader election. /// @@ -262,7 +262,9 @@ impl RaftRoleState for CandidateState { // Step down as Follower self.send_become_follower_event(&role_tx)?; - info!("Candiate will not process ReceiveVoteRequest, it should let Follower do it."); + info!( + "Candiate will not process ReceiveVoteRequest, it should let Follower do it." + ); self.send_replay_raft_event( &role_tx, RaftEvent::ReceiveVoteRequest(vote_request, sender), @@ -373,7 +375,9 @@ impl RaftRoleState for CandidateState { // Step down as Follower self.send_become_follower_event(&role_tx)?; - info!("Candiate will not process AppendEntries request, it should let Follower do it."); + info!( + "Candiate will not process AppendEntries request, it should let Follower do it." + ); self.send_replay_raft_event( &role_tx, RaftEvent::AppendEntries(append_entries_request, sender), @@ -410,8 +414,8 @@ impl RaftRoleState for CandidateState { None => { // Policy requires leader access - reject let error = tonic::Status::permission_denied( - "Read consistency policy requires leader access. Current node is candidate." - ); + "Read consistency policy requires leader access. Current node is candidate.", + ); sender.send(Err(error)).map_err(|e| { error!("Failed to send policy rejection: {:?}", e); NetworkError::SingalSendFailed(format!("{e:?}")) @@ -460,7 +464,7 @@ impl RaftRoleState for CandidateState { ctx.node_id ), } - .into()) + .into()); } RaftEvent::SnapshotCreated(_result) => { @@ -472,7 +476,7 @@ impl RaftRoleState for CandidateState { ctx.node_id ), } - .into()) + .into()); } RaftEvent::LogPurgeCompleted(_purged_id) => { @@ -484,7 +488,7 @@ impl RaftRoleState for CandidateState { ctx.node_id ), } - .into()) + .into()); } RaftEvent::JoinCluster(_join_request, sender) => { @@ -602,7 +606,7 @@ impl CandidateState { current_term: self.current_term(), voted_for: None, commit_index: self.commit_index(), - role: CANDIDATE, + role: Candidate, } } diff --git a/src/core/raft_role/candidate_state_test.rs b/d-engine-core/src/raft_role/candidate_state_test.rs similarity index 95% rename from src/core/raft_role/candidate_state_test.rs rename to d-engine-core/src/raft_role/candidate_state_test.rs index ac97beb0..d05bfcb2 100644 --- a/src/core/raft_role/candidate_state_test.rs +++ b/d-engine-core/src/raft_role/candidate_state_test.rs @@ -8,31 +8,6 @@ use tonic::Status; use tracing_test::traced_test; use super::candidate_state::CandidateState; -use crate::proto::client::ClientReadRequest; -use crate::proto::client::ClientWriteRequest; -use crate::proto::client::ReadConsistencyPolicy; -use crate::proto::cluster::cluster_conf_update_response; -use crate::proto::cluster::ClusterConfChangeRequest; -use crate::proto::cluster::ClusterConfUpdateResponse; -use crate::proto::cluster::ClusterMembership; -use crate::proto::cluster::JoinRequest; -use crate::proto::cluster::LeaderDiscoveryRequest; -use crate::proto::cluster::MetadataRequest; -use crate::proto::common::LogId; -use crate::proto::election::VoteRequest; -use crate::proto::election::VoteResponse; -use crate::proto::election::VotedFor; -use crate::proto::error::ErrorCode; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::replication::AppendEntriesResponse; -use crate::proto::storage::PurgeLogRequest; -use crate::role_state::RaftRoleState; -use crate::test_utils::crate_test_snapshot_stream; -use crate::test_utils::create_test_chunk; -use crate::test_utils::mock_election_core; -use crate::test_utils::mock_raft_context; -use crate::test_utils::setup_raft_components; -use crate::test_utils::MockTypeConfig; use crate::ConsensusError; use crate::ElectionError; use crate::Error; @@ -45,7 +20,32 @@ use crate::MockStateMachineHandler; use crate::RaftEvent; use crate::RaftOneshot; use crate::RoleEvent; -use crate::LEARNER; +use crate::role_state::RaftRoleState; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::crate_test_snapshot_stream; +use crate::test_utils::create_test_chunk; +use crate::test_utils::mock_election_core; +use crate::test_utils::mock_raft_context; +use crate::test_utils::setup_raft_components; +use d_engine_proto::client::ClientReadRequest; +use d_engine_proto::client::ClientWriteRequest; +use d_engine_proto::client::ReadConsistencyPolicy; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Learner; +use d_engine_proto::error::ErrorCode; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::JoinRequest; +use d_engine_proto::server::cluster::LeaderDiscoveryRequest; +use d_engine_proto::server::cluster::MetadataRequest; +use d_engine_proto::server::cluster::cluster_conf_update_response; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::election::VotedFor; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::storage::PurgeLogRequest; /// # Case 1: Can vote myself #[tokio::test] @@ -579,20 +579,22 @@ fn test_send_replay_raft_event() { let (role_tx, mut role_rx) = mpsc::unbounded_channel(); let (resp_tx, _resp_rx) = MaybeCloneOneshot::new(); assert!(state.send_become_follower_event(&role_tx).is_ok()); - assert!(state - .send_replay_raft_event( - &role_tx, - RaftEvent::ReceiveVoteRequest( - VoteRequest { - term: 1, - candidate_id: 1, - last_log_index: 0, - last_log_term: 0, - }, - resp_tx, - ), - ) - .is_ok()); + assert!( + state + .send_replay_raft_event( + &role_tx, + RaftEvent::ReceiveVoteRequest( + VoteRequest { + term: 1, + candidate_id: 1, + last_log_index: 0, + last_log_term: 0, + }, + resp_tx, + ), + ) + .is_ok() + ); assert!(matches!( role_rx.try_recv().unwrap(), @@ -684,7 +686,7 @@ async fn test_handle_raft_event_case10() { // Step 2: Prepare the event let request = JoinRequest { node_id: 2, - node_role: LEARNER, + node_role: Learner, address: "127.0.0.1:9090".to_string(), }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); @@ -796,11 +798,11 @@ mod role_violation_tests { #[cfg(test)] mod handle_client_read_request { use super::*; + use crate::RaftNodeConfig; use crate::config::ReadConsistencyPolicy as ServerPolicy; use crate::convert::safe_kv_bytes; - use crate::proto::client::ReadConsistencyPolicy as ClientPolicy; use crate::test_utils::MockBuilder; - use crate::RaftNodeConfig; + use d_engine_proto::client::ReadConsistencyPolicy as ClientPolicy; /// # Case 6.1: test ClientReadRequest with linear request #[tokio::test] diff --git a/src/core/raft_role/follower_state.rs b/d-engine-core/src/raft_role/follower_state.rs similarity index 97% rename from src/core/raft_role/follower_state.rs rename to d-engine-core/src/raft_role/follower_state.rs index f5a78b08..c4f6bbce 100644 --- a/src/core/raft_role/follower_state.rs +++ b/d-engine-core/src/raft_role/follower_state.rs @@ -4,8 +4,8 @@ use std::sync::Arc; use tokio::sync::mpsc; use tokio::time::Instant; -use tonic::async_trait; use tonic::Status; +use tonic::async_trait; use tracing::debug; use tracing::error; use tracing::info; @@ -13,26 +13,15 @@ use tracing::instrument; use tracing::trace; use tracing::warn; +use super::HardState; +use super::RaftRole; +use super::SharedState; +use super::StateSnapshot; use super::can_serve_read_locally; use super::candidate_state::CandidateState; use super::leader_state::LeaderState; use super::learner_state::LearnerState; use super::role_state::RaftRoleState; -use super::HardState; -use super::RaftRole; -use super::SharedState; -use super::StateSnapshot; -use super::FOLLOWER; -use crate::proto::client::ClientResponse; -use crate::proto::cluster::ClusterConfUpdateResponse; -use crate::proto::common::LogId; -use crate::proto::election::VoteResponse; -use crate::proto::error::ErrorCode; -use crate::proto::storage::snapshot_ack::ChunkStatus; -use crate::proto::storage::PurgeLogResponse; -use crate::proto::storage::SnapshotAck; -use crate::proto::storage::SnapshotResponse; -use crate::utils::cluster::error; use crate::ConsensusError; use crate::ElectionCore; use crate::ElectionTimer; @@ -48,6 +37,17 @@ use crate::RoleEvent; use crate::StateMachineHandler; use crate::StateTransitionError; use crate::TypeConfig; +use crate::utils::cluster::error; +use d_engine_proto::client::ClientResponse; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::error::ErrorCode; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::storage::PurgeLogResponse; +use d_engine_proto::server::storage::SnapshotAck; +use d_engine_proto::server::storage::SnapshotResponse; +use d_engine_proto::server::storage::snapshot_ack::ChunkStatus; /// Follower node's state in Raft consensus. /// @@ -360,8 +360,8 @@ impl RaftRoleState for FollowerState { None => { // Policy requires leader access - reject let error = tonic::Status::permission_denied( - "Read consistency policy requires leader access. Current node is follower." - ); + "Read consistency policy requires leader access. Current node is follower.", + ); sender.send(Err(error)).map_err(|e| { error!("Failed to send policy rejection: {:?}", e); NetworkError::SingalSendFailed(format!("{e:?}")) @@ -497,7 +497,7 @@ impl RaftRoleState for FollowerState { ctx.node_id ), } - .into()) + .into()); } RaftEvent::CreateSnapshotEvent => { @@ -509,7 +509,7 @@ impl RaftRoleState for FollowerState { ctx.node_id ), } - .into()) + .into()); } RaftEvent::SnapshotCreated(_result) => { @@ -521,7 +521,7 @@ impl RaftRoleState for FollowerState { ctx.node_id ), } - .into()) + .into()); } RaftEvent::JoinCluster(_join_request, sender) => { @@ -634,7 +634,7 @@ impl FollowerState { #[tracing::instrument] pub fn state_snapshot(&self) -> StateSnapshot { StateSnapshot { - role: FOLLOWER, + role: Follower, current_term: self.current_term(), voted_for: None, commit_index: self.commit_index(), diff --git a/src/core/raft_role/follower_state_test.rs b/d-engine-core/src/raft_role/follower_state_test.rs similarity index 98% rename from src/core/raft_role/follower_state_test.rs rename to d-engine-core/src/raft_role/follower_state_test.rs index 4fda37db..ed518039 100644 --- a/src/core/raft_role/follower_state_test.rs +++ b/d-engine-core/src/raft_role/follower_state_test.rs @@ -8,33 +8,8 @@ use tonic::Code; use tonic::Status; use tracing_test::traced_test; -use super::follower_state::FollowerState; use super::HardState; -use crate::proto::client::ClientReadRequest; -use crate::proto::client::ClientWriteRequest; -use crate::proto::client::ReadConsistencyPolicy; -use crate::proto::cluster::cluster_conf_update_response; -use crate::proto::cluster::ClusterConfChangeRequest; -use crate::proto::cluster::ClusterConfUpdateResponse; -use crate::proto::cluster::ClusterMembership; -use crate::proto::cluster::JoinRequest; -use crate::proto::cluster::LeaderDiscoveryRequest; -use crate::proto::cluster::MetadataRequest; -use crate::proto::common::LogId; -use crate::proto::election::VoteRequest; -use crate::proto::election::VoteResponse; -use crate::proto::election::VotedFor; -use crate::proto::error::ErrorCode; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::replication::AppendEntriesResponse; -use crate::proto::storage::PurgeLogRequest; -use crate::role_state::RaftRoleState; -use crate::test_utils::mock_raft_context; -use crate::test_utils::node_config; -use crate::test_utils::setup_raft_components; -use crate::test_utils::MockBuilder; -use crate::test_utils::MockStorageEngine; -use crate::test_utils::MockTypeConfig; +use super::follower_state::FollowerState; use crate::AppendResponseWithUpdates; use crate::ConsensusError; use crate::Error; @@ -56,7 +31,32 @@ use crate::RoleEvent; use crate::SnapshotError; use crate::StateUpdate; use crate::SystemError; -use crate::LEARNER; +use crate::role_state::RaftRoleState; +use crate::test_utils::MockBuilder; +use crate::test_utils::MockStorageEngine; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::mock_raft_context; +use crate::test_utils::node_config; +use crate::test_utils::setup_raft_components; +use d_engine_proto::client::ClientReadRequest; +use d_engine_proto::client::ClientWriteRequest; +use d_engine_proto::client::ReadConsistencyPolicy; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Learner; +use d_engine_proto::error::ErrorCode; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::JoinRequest; +use d_engine_proto::server::cluster::LeaderDiscoveryRequest; +use d_engine_proto::server::cluster::MetadataRequest; +use d_engine_proto::server::cluster::cluster_conf_update_response; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::election::VotedFor; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::storage::PurgeLogRequest; /// # Case 1: assume it is fresh cluster start /// @@ -1449,7 +1449,7 @@ async fn test_handle_raft_event_case10() { // Step 2: Prepare the event let request = JoinRequest { node_id: 2, - node_role: LEARNER, + node_role: Learner, address: "127.0.0.1:9090".to_string(), }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); @@ -1688,10 +1688,10 @@ mod role_violation_tests { #[cfg(test)] mod handle_client_read_request { use super::*; + use crate::RaftNodeConfig; use crate::config::ReadConsistencyPolicy as ServerPolicy; use crate::convert::safe_kv_bytes; - use crate::proto::client::ReadConsistencyPolicy as ClientPolicy; - use crate::RaftNodeConfig; + use d_engine_proto::client::ReadConsistencyPolicy as ClientPolicy; /// Test that follower rejects LeaseRead policy #[tokio::test] diff --git a/src/core/raft_role/leader_state.rs b/d-engine-core/src/raft_role/leader_state.rs similarity index 98% rename from src/core/raft_role/leader_state.rs rename to d-engine-core/src/raft_role/leader_state.rs index 1251db53..7d0cf48c 100644 --- a/src/core/raft_role/leader_state.rs +++ b/d-engine-core/src/raft_role/leader_state.rs @@ -2,20 +2,20 @@ use std::collections::HashMap; use std::collections::VecDeque; use std::fmt::Debug; use std::marker::PhantomData; +use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; -use std::sync::Arc; use std::time::Duration; use nanoid::nanoid; use tokio::sync::mpsc; use tokio::sync::oneshot; +use tokio::time::Instant; use tokio::time::sleep; use tokio::time::timeout; -use tokio::time::Instant; -use tonic::async_trait; use tonic::Status; +use tonic::async_trait; use tracing::debug; use tracing::error; use tracing::info; @@ -23,44 +23,12 @@ use tracing::instrument; use tracing::trace; use tracing::warn; -use super::candidate_state::CandidateState; -use super::role_state::RaftRoleState; use super::LeaderStateSnapshot; use super::RaftRole; use super::SharedState; use super::StateSnapshot; -use super::LEADER; -use crate::alias::MOF; -use crate::alias::ROF; -use crate::alias::SMHOF; -use crate::client_command_to_entry_payloads; -use crate::cluster::is_majority; -use crate::ensure_safe_join; -use crate::grpc::BackgroundSnapshotTransfer; -use crate::proto::client::ClientResponse; -use crate::proto::client::ReadConsistencyPolicy as ClientReadConsistencyPolicy; -use crate::proto::cluster::ClusterConfUpdateResponse; -use crate::proto::cluster::JoinRequest; -use crate::proto::cluster::JoinResponse; -use crate::proto::cluster::LeaderDiscoveryResponse; -use crate::proto::common::membership_change::Change; -use crate::proto::common::AddNode; -use crate::proto::common::BatchPromote; -use crate::proto::common::BatchRemove; -use crate::proto::common::EntryPayload; -use crate::proto::common::LogId; -use crate::proto::common::NodeStatus; -use crate::proto::election::VoteResponse; -use crate::proto::election::VotedFor; -use crate::proto::error::ErrorCode; -use crate::proto::replication::AppendEntriesResponse; -use crate::proto::storage::PurgeLogRequest; -use crate::proto::storage::PurgeLogResponse; -use crate::proto::storage::SnapshotChunk; -use crate::proto::storage::SnapshotMetadata; -use crate::scoped_timer::ScopedTimer; -use crate::stream::create_production_snapshot_stream; -use crate::utils::cluster::error; +use super::candidate_state::CandidateState; +use super::role_state::RaftRoleState; use crate::AppendResults; use crate::BatchBuffer; use crate::ConnectionType; @@ -93,6 +61,38 @@ use crate::StateMachineHandler; use crate::StateTransitionError; use crate::Transport; use crate::TypeConfig; +use crate::alias::MOF; +use crate::alias::ROF; +use crate::alias::SMHOF; +use crate::client_command_to_entry_payloads; +use crate::cluster::is_majority; +use crate::ensure_safe_join; +use crate::grpc::BackgroundSnapshotTransfer; +use crate::scoped_timer::ScopedTimer; +use crate::stream::create_production_snapshot_stream; +use crate::utils::cluster::error; +use d_engine_proto::client::ClientResponse; +use d_engine_proto::client::ReadConsistencyPolicy as ClientReadConsistencyPolicy; +use d_engine_proto::common::AddNode; +use d_engine_proto::common::BatchPromote; +use d_engine_proto::common::BatchRemove; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Leader; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::common::membership_change::Change; +use d_engine_proto::error::ErrorCode; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::cluster::JoinRequest; +use d_engine_proto::server::cluster::JoinResponse; +use d_engine_proto::server::cluster::LeaderDiscoveryResponse; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::election::VotedFor; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::storage::PurgeLogRequest; +use d_engine_proto::server::storage::PurgeLogResponse; +use d_engine_proto::server::storage::SnapshotChunk; +use d_engine_proto::server::storage::SnapshotMetadata; // Supporting data structures #[derive(Debug, Clone)] @@ -739,7 +739,9 @@ impl RaftRoleState for LeaderState { //TODO: if there is a bug? self.update_current_term(vote_request.term); self.send_become_follower_event(Some(cluste_conf_change_request.id), &role_tx)?; - info!("Leader will not process append_entries_request, it should let Follower do it."); + info!( + "Leader will not process append_entries_request, it should let Follower do it." + ); send_replay_raft_event( &role_tx, RaftEvent::ClusterConfUpdate(cluste_conf_change_request, sender), @@ -774,7 +776,9 @@ impl RaftRoleState for LeaderState { &role_tx, )?; - info!("Leader will not process append_entries_request, it should let Follower do it."); + info!( + "Leader will not process append_entries_request, it should let Follower do it." + ); send_replay_raft_event( &role_tx, RaftEvent::AppendEntries(append_entries_request, sender), @@ -864,7 +868,9 @@ impl RaftRoleState for LeaderState { &ctx.handlers.state_machine_handler, last_applied_index, ) { - warn!("ensure_state_machine_upto_commit_index failed for linear read request"); + warn!( + "ensure_state_machine_upto_commit_index failed for linear read request" + ); Err(tonic::Status::failed_precondition(format!( "ensure_state_machine_upto_commit_index failed: {e:?}" ))) @@ -1196,7 +1202,7 @@ impl LeaderState { current_term: self.current_term(), voted_for: None, commit_index: self.commit_index(), - role: LEADER, + role: Leader, } } @@ -1333,7 +1339,7 @@ impl LeaderState { new_commit_index ); self.update_commit_index_with_signal( - LEADER, + Leader, self.current_term(), new_commit_index, role_tx, @@ -1422,8 +1428,7 @@ impl LeaderState { } trace!( "Updated next index for peer {}-{}", - peer_id, - update.next_index + peer_id, update.next_index ); if let Some(match_index) = update.match_index { if let Err(e) = self.update_match_index(*peer_id, match_index) { diff --git a/src/core/raft_role/leader_state_test.rs b/d-engine-core/src/raft_role/leader_state_test.rs similarity index 97% rename from src/core/raft_role/leader_state_test.rs rename to d-engine-core/src/raft_role/leader_state_test.rs index 76cff76f..5c980901 100644 --- a/src/core/raft_role/leader_state_test.rs +++ b/d-engine-core/src/raft_role/leader_state_test.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::collections::VecDeque; -use std::sync::atomic::Ordering; use std::sync::Arc; +use std::sync::atomic::Ordering; use bytes::Bytes; use futures::StreamExt; @@ -9,43 +9,13 @@ use mockall::predicate::eq; use nanoid::nanoid; use tokio::sync::mpsc; use tokio::sync::watch; -use tonic::transport::Endpoint; use tonic::Code; use tonic::Status; +use tonic::transport::Endpoint; use tracing_test::traced_test; use super::leader_state::LeaderState; use super::role_state::RaftRoleState; -use crate::client_command_to_entry_payloads; -use crate::convert::safe_kv_bytes; -use crate::proto::client::ClientReadRequest; -use crate::proto::client::ClientResponse; -use crate::proto::client::ClientWriteRequest; -use crate::proto::client::ReadConsistencyPolicy; -use crate::proto::cluster::ClusterConfChangeRequest; -use crate::proto::cluster::ClusterMembership; -use crate::proto::cluster::JoinRequest; -use crate::proto::cluster::LeaderDiscoveryRequest; -use crate::proto::cluster::MetadataRequest; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::EntryPayload; -use crate::proto::common::LogId; -use crate::proto::common::NodeStatus; -use crate::proto::election::VoteRequest; -use crate::proto::election::VoteResponse; -use crate::proto::error::ErrorCode; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::storage::PurgeLogRequest; -use crate::proto::storage::PurgeLogResponse; -use crate::proto::storage::SnapshotMetadata; -use crate::test_utils::crate_test_snapshot_stream; -use crate::test_utils::create_test_chunk; -use crate::test_utils::mock_raft_context; -use crate::test_utils::node_config; -use crate::test_utils::setup_raft_components; -use crate::test_utils::MockBuilder; -use crate::test_utils::MockStorageEngine; -use crate::test_utils::MockTypeConfig; use crate::AppendResults; use crate::ConsensusError; use crate::Error; @@ -71,9 +41,39 @@ use crate::RaftTypeConfig; use crate::ReplicationError; use crate::RoleEvent; use crate::SnapshotError; -use crate::FOLLOWER; -use crate::LEADER; -use crate::LEARNER; +use crate::client_command_to_entry_payloads; +use crate::convert::safe_kv_bytes; +use crate::test_utils::MockBuilder; +use crate::test_utils::MockStorageEngine; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::crate_test_snapshot_stream; +use crate::test_utils::create_test_chunk; +use crate::test_utils::mock_raft_context; +use crate::test_utils::node_config; +use crate::test_utils::setup_raft_components; +use d_engine_proto::client::ClientReadRequest; +use d_engine_proto::client::ClientResponse; +use d_engine_proto::client::ClientWriteRequest; +use d_engine_proto::client::ReadConsistencyPolicy; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeRole::Leader; +use d_engine_proto::common::NodeRole::Learner; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::error::ErrorCode; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::JoinRequest; +use d_engine_proto::server::cluster::LeaderDiscoveryRequest; +use d_engine_proto::server::cluster::MetadataRequest; +use d_engine_proto::server::cluster::NodeMeta; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::storage::PurgeLogRequest; +use d_engine_proto::server::storage::PurgeLogResponse; +use d_engine_proto::server::storage::SnapshotMetadata; struct ProcessRaftRequestTestContext { state: LeaderState, @@ -364,20 +364,22 @@ async fn test_process_raft_request_case2() { let (resp_tx, _resp_rx) = MaybeCloneOneshot::new(); let (role_tx, _role_rx) = mpsc::unbounded_channel(); - assert!(test_context - .state - .process_raft_request( - RaftRequestWithSignal { - id: nanoid!(), - payloads: client_command_to_entry_payloads(client_propose_request.commands), - sender: resp_tx, - }, - &test_context.raft_context, - false, - &role_tx - ) - .await - .is_ok()); + assert!( + test_context + .state + .process_raft_request( + RaftRequestWithSignal { + id: nanoid!(), + payloads: client_command_to_entry_payloads(client_propose_request.commands), + sender: resp_tx, + }, + &test_context.raft_context, + false, + &role_tx + ) + .await + .is_ok() + ); } /// # Case 1: state_machine_handler::update_pending should be executed once @@ -1161,7 +1163,7 @@ mod snapshot_created_event_tests { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -1213,7 +1215,7 @@ mod snapshot_created_event_tests { vec![NodeMeta { id: 2, address: "".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -1355,7 +1357,7 @@ mod snapshot_created_event_tests { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -1429,7 +1431,7 @@ mod snapshot_created_event_tests { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -1501,13 +1503,13 @@ mod snapshot_created_event_tests { NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, NodeMeta { id: 3, address: "http://127.0.0.1:55002".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, ] @@ -1653,7 +1655,7 @@ async fn test_handle_raft_event_case10_1_discover_leader_success() { Some(NodeMeta { id: 1, address: "127.0.0.1:50051".to_string(), - role: LEADER, + role: Leader, status: NodeStatus::Active.into(), }) }); @@ -1733,7 +1735,7 @@ async fn test_handle_raft_event_case10_4_different_leader_terms() { Some(NodeMeta { id: 1, address: "127.0.0.1:50051".to_string(), - role: LEADER, + role: Leader, status: NodeStatus::Active.into(), }) }); @@ -1779,7 +1781,7 @@ async fn test_handle_raft_event_case10_5_invalid_node_id() { Some(NodeMeta { id: 1, address: "127.0.0.1:50051".to_string(), - role: LEADER, + role: Leader, status: NodeStatus::Active.into(), }) }); @@ -2137,13 +2139,13 @@ async fn test_process_batch_case2_2_quorum_non_verifiable_failure() { vec![ NodeMeta { id: 2, - role: FOLLOWER, + role: Follower, address: "127.0.0.1:0".to_string(), status: NodeStatus::Active.into(), }, NodeMeta { id: 3, - role: FOLLOWER, + role: Follower, address: "127.0.0.1:0".to_string(), status: NodeStatus::Active.into(), }, @@ -2251,13 +2253,13 @@ async fn test_process_batch_case4_partial_timeouts() { NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, NodeMeta { id: 3, address: "http://127.0.0.1:55002".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, ] @@ -2311,13 +2313,13 @@ async fn test_process_batch_case5_all_timeout() { NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, NodeMeta { id: 3, address: "http://127.0.0.1:55002".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, ] @@ -2520,19 +2522,19 @@ async fn test_verify_internal_quorum_case3_non_verifiable_failure() { NodeMeta { id: 2, address: "".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, NodeMeta { id: 3, address: "".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, NodeMeta { id: 4, address: "".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, ] @@ -2723,7 +2725,7 @@ async fn test_handle_join_cluster_case1_success() { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -2786,7 +2788,7 @@ async fn test_handle_join_cluster_case1_success() { .handle_join_cluster( JoinRequest { node_id, - node_role: LEARNER, + node_role: Learner, address: address.clone(), }, sender, @@ -2828,7 +2830,7 @@ async fn test_handle_join_cluster_case2_node_exists() { .handle_join_cluster( JoinRequest { node_id, - node_role: LEARNER, + node_role: Learner, address: "127.0.0.1:8080".to_string(), }, sender, @@ -2884,7 +2886,7 @@ async fn test_handle_join_cluster_case3_quorum_failed() { .handle_join_cluster( JoinRequest { node_id, - node_role: LEARNER, + node_role: Learner, address: "127.0.0.1:8080".to_string(), }, sender, @@ -2921,7 +2923,7 @@ async fn test_handle_join_cluster_case4_quorum_error() { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -2941,7 +2943,7 @@ async fn test_handle_join_cluster_case4_quorum_error() { .handle_join_cluster( JoinRequest { node_id, - node_role: LEARNER, + node_role: Learner, address: "127.0.0.1:8080".to_string(), }, sender, @@ -2984,7 +2986,7 @@ async fn test_handle_join_cluster_case5_snapshot_triggered() { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -3041,7 +3043,7 @@ async fn test_handle_join_cluster_case5_snapshot_triggered() { .handle_join_cluster( JoinRequest { node_id, - node_role: LEARNER, + node_role: Learner, address, }, sender, @@ -3062,10 +3064,10 @@ mod trigger_background_snapshot_test { use futures::stream; use super::*; - use crate::core::raft_role::leader_state::LeaderState; - use crate::proto::storage::SnapshotChunk; - use crate::proto::storage::SnapshotMetadata; use crate::SnapshotConfig; + use crate::core::raft_role::leader_state::LeaderState; + use d_engine_proto::server::storage::SnapshotChunk; + use d_engine_proto::server::storage::SnapshotMetadata; fn mock_membership(should_fail: bool) -> MockMembership { let mut membership = MockMembership::::new(); @@ -3167,11 +3169,11 @@ mod batch_promote_learners_test { use mockall::predicate::*; use super::*; - use crate::core::raft_role::leader_state::LeaderState; + use crate::RaftContext; use crate::core::raft_role::RoleEvent; + use crate::core::raft_role::leader_state::LeaderState; use crate::membership::MockMembership; - use crate::proto::common::NodeStatus; - use crate::RaftContext; + use d_engine_proto::common::NodeStatus; enum VerifyInternalQuorumWithRetrySuccess { Success, @@ -3208,7 +3210,7 @@ mod batch_promote_learners_test { .map(|id| NodeMeta { id: id as u32, address: format!("addr_{id}"), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }) .collect() @@ -3366,12 +3368,12 @@ mod pending_promotion_tests { use bytes::Bytes; use parking_lot::Mutex; - use tokio::time::timeout; use tokio::time::Instant; + use tokio::time::timeout; use super::*; - use crate::leader_state::calculate_safe_batch_size; use crate::leader_state::PendingPromotion; + use crate::leader_state::calculate_safe_batch_size; // Test fixture struct TestFixture { @@ -3409,7 +3411,7 @@ mod pending_promotion_tests { id, address: "".to_string(), status: NodeStatus::Active as i32, - role: FOLLOWER, + role: Follower, }) .collect() }); @@ -3579,11 +3581,13 @@ mod pending_promotion_tests { #[tokio::test] async fn test_handle_stale_learner() { let mut fixture = TestFixture::new("test_handle_stale_learner", true).await; - assert!(fixture - .leader_state - .handle_stale_learner(1, &fixture.raft_context) - .await - .is_ok()); + assert!( + fixture + .leader_state + .handle_stale_learner(1, &fixture.raft_context) + .await + .is_ok() + ); } #[tokio::test] @@ -3599,7 +3603,7 @@ mod pending_promotion_tests { id, address: "".to_string(), status: NodeStatus::Active as i32, - role: FOLLOWER, + role: Follower, }) .collect() }); @@ -3743,10 +3747,10 @@ mod stale_learner_tests { use tokio::time::Instant; use super::*; + use crate::RaftNodeConfig; use crate::leader_state::PendingPromotion; use crate::test_utils; - use crate::test_utils::*; - use crate::RaftNodeConfig; // Assuming you have test utilities + use crate::test_utils::*; // Assuming you have test utilities // Setup helper fn create_test_leader_state( @@ -3947,7 +3951,7 @@ mod handle_client_read_request { use super::*; use crate::config::ReadConsistencyPolicy as ServerPolicy; use crate::convert::safe_kv_bytes; - use crate::proto::client::ReadConsistencyPolicy as ClientPolicy; + use d_engine_proto::client::ReadConsistencyPolicy as ClientPolicy; /// Test LeaseRead policy with valid lease #[tokio::test] @@ -4177,8 +4181,8 @@ mod handle_client_read_request { #[cfg(test)] mod lease_validity_tests { use super::*; - use crate::test_utils::MockBuilder; use crate::RaftNodeConfig; + use crate::test_utils::MockBuilder; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::sync::watch; diff --git a/src/core/raft_role/learner_state.rs b/d-engine-core/src/raft_role/learner_state.rs similarity index 96% rename from src/core/raft_role/learner_state.rs rename to d-engine-core/src/raft_role/learner_state.rs index 29e66d04..4c3d895c 100644 --- a/src/core/raft_role/learner_state.rs +++ b/d-engine-core/src/raft_role/learner_state.rs @@ -5,34 +5,20 @@ use std::time::Duration; use tokio::sync::mpsc::{self}; use tokio::time::Instant; -use tonic::async_trait; use tonic::Status; +use tonic::async_trait; use tracing::debug; use tracing::error; use tracing::info; use tracing::trace; use tracing::warn; -use super::candidate_state::CandidateState; -use super::follower_state::FollowerState; -use super::role_state::RaftRoleState; use super::RaftRole; use super::SharedState; use super::StateSnapshot; -use super::LEARNER; -use crate::alias::MOF; -use crate::proto::client::ClientResponse; -use crate::proto::cluster::ClusterConfUpdateResponse; -use crate::proto::cluster::JoinRequest; -use crate::proto::cluster::LeaderDiscoveryRequest; -use crate::proto::cluster::LeaderDiscoveryResponse; -use crate::proto::common::LogId; -use crate::proto::election::VoteResponse; -use crate::proto::election::VotedFor; -use crate::proto::error::ErrorCode; -use crate::proto::storage::snapshot_ack::ChunkStatus; -use crate::proto::storage::SnapshotAck; -use crate::proto::storage::SnapshotResponse; +use super::candidate_state::CandidateState; +use super::follower_state::FollowerState; +use super::role_state::RaftRoleState; use crate::ConsensusError; use crate::Membership; use crate::MembershipError; @@ -47,6 +33,20 @@ use crate::StateMachineHandler; use crate::StateTransitionError; use crate::Transport; use crate::TypeConfig; +use crate::alias::MOF; +use d_engine_proto::client::ClientResponse; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Learner; +use d_engine_proto::error::ErrorCode; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::cluster::JoinRequest; +use d_engine_proto::server::cluster::LeaderDiscoveryRequest; +use d_engine_proto::server::cluster::LeaderDiscoveryResponse; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::election::VotedFor; +use d_engine_proto::server::storage::SnapshotAck; +use d_engine_proto::server::storage::SnapshotResponse; +use d_engine_proto::server::storage::snapshot_ack::ChunkStatus; /// Learner node's state in Raft cluster. /// @@ -344,7 +344,7 @@ impl RaftRoleState for LearnerState { required_role: "Leader", context: format!("Learner node {} attempted to create snapshot.", ctx.node_id), } - .into()) + .into()); } RaftEvent::SnapshotCreated(_result) => { @@ -356,7 +356,7 @@ impl RaftRoleState for LearnerState { ctx.node_id ), } - .into()) + .into()); } RaftEvent::LogPurgeCompleted(_purged_id) => { @@ -368,7 +368,7 @@ impl RaftRoleState for LearnerState { ctx.node_id ), } - .into()) + .into()); } RaftEvent::JoinCluster(_join_request, sender) => { @@ -472,7 +472,7 @@ impl RaftRoleState for LearnerState { leader_id, JoinRequest { node_id: node_config.cluster.node_id, - node_role: LEARNER, + node_role: Learner, address: node_config.cluster.listen_address.to_string(), }, node_config.retry.join_cluster, @@ -537,7 +537,7 @@ impl LearnerState { current_term: self.current_term(), voted_for: None, commit_index: self.commit_index(), - role: LEARNER, + role: Learner, } } } diff --git a/src/core/raft_role/learner_state_test.rs b/d-engine-core/src/raft_role/learner_state_test.rs similarity index 97% rename from src/core/raft_role/learner_state_test.rs rename to d-engine-core/src/raft_role/learner_state_test.rs index 42070f52..beb88fcc 100644 --- a/src/core/raft_role/learner_state_test.rs +++ b/d-engine-core/src/raft_role/learner_state_test.rs @@ -9,27 +9,6 @@ use tonic::Code; use tracing::debug; use tracing_test::traced_test; -use crate::learner_state::LearnerState; -use crate::proto::client::ClientReadRequest; -use crate::proto::client::ClientWriteRequest; -use crate::proto::cluster::cluster_conf_update_response; -use crate::proto::cluster::ClusterConfChangeRequest; -use crate::proto::cluster::ClusterConfUpdateResponse; -use crate::proto::cluster::JoinRequest; -use crate::proto::cluster::JoinResponse; -use crate::proto::cluster::LeaderDiscoveryRequest; -use crate::proto::cluster::LeaderDiscoveryResponse; -use crate::proto::cluster::MetadataRequest; -use crate::proto::common::LogId; -use crate::proto::election::VoteRequest; -use crate::proto::error::ErrorCode; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::replication::AppendEntriesResponse; -use crate::proto::storage::PurgeLogRequest; -use crate::role_state::RaftRoleState; -use crate::test_utils::mock_membership; -use crate::test_utils::mock_raft_context; -use crate::test_utils::MockTypeConfig; use crate::AppendResponseWithUpdates; use crate::ConsensusError; use crate::Error; @@ -45,7 +24,28 @@ use crate::RaftEvent; use crate::RaftOneshot; use crate::RoleEvent; use crate::SystemError; -use crate::LEARNER; +use crate::learner_state::LearnerState; +use crate::role_state::RaftRoleState; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::mock_membership; +use crate::test_utils::mock_raft_context; +use d_engine_proto::client::ClientReadRequest; +use d_engine_proto::client::ClientWriteRequest; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Learner; +use d_engine_proto::error::ErrorCode; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::cluster::JoinRequest; +use d_engine_proto::server::cluster::JoinResponse; +use d_engine_proto::server::cluster::LeaderDiscoveryRequest; +use d_engine_proto::server::cluster::LeaderDiscoveryResponse; +use d_engine_proto::server::cluster::MetadataRequest; +use d_engine_proto::server::cluster::cluster_conf_update_response; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::storage::PurgeLogRequest; /// Validate Follower step up as Candidate in new election round #[tokio::test] @@ -516,7 +516,7 @@ async fn test_handle_raft_event_case10() { // Step 2: Prepare the event let request = JoinRequest { node_id: 2, - node_role: LEARNER, + node_role: Learner, address: "127.0.0.1:9090".to_string(), }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); diff --git a/src/core/raft_role/mod.rs b/d-engine-core/src/raft_role/mod.rs similarity index 96% rename from src/core/raft_role/mod.rs rename to d-engine-core/src/raft_role/mod.rs index 69b9f5cc..2f275f2a 100644 --- a/src/core/raft_role/mod.rs +++ b/d-engine-core/src/raft_role/mod.rs @@ -13,12 +13,6 @@ mod leader_state_test; #[cfg(test)] mod learner_state_test; -//--------------------------------------------------------------------- -pub const FOLLOWER: i32 = 0; -pub const CANDIDATE: i32 = 1; -pub const LEADER: i32 = 2; -pub const LEARNER: i32 = 3; - use std::collections::HashMap; use candidate_state::CandidateState; @@ -26,11 +20,11 @@ use follower_state::FollowerState; use leader_state::LeaderState; use learner_state::LearnerState; use role_state::RaftRoleState; -use serde::ser::SerializeStruct; use serde::Deserialize; use serde::Deserializer; use serde::Serialize; use serde::Serializer; +use serde::ser::SerializeStruct; use tokio::sync::mpsc; use tokio::time::Instant; use tracing::debug; @@ -39,11 +33,11 @@ use tracing::trace; use super::RaftContext; use super::RaftEvent; use super::RoleEvent; -use crate::proto::client::ClientReadRequest; -use crate::proto::common::EntryPayload; -use crate::proto::election::VotedFor; use crate::Result; use crate::TypeConfig; +use d_engine_proto::client::ClientReadRequest; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::server::election::VotedFor; /// The role state focuses solely on its own logic /// and does not directly manipulate the underlying storage or network. @@ -362,7 +356,7 @@ pub(crate) enum QuorumVerificationResult { } use crate::config::ReadConsistencyPolicy as ServerReadConsistencyPolicy; -use crate::proto::client::ReadConsistencyPolicy as ClientReadConsistencyPolicy; +use d_engine_proto::client::ReadConsistencyPolicy as ClientReadConsistencyPolicy; /// Checks if non-leader node can serve read request locally /// diff --git a/src/core/raft_role/role_state.rs b/d-engine-core/src/raft_role/role_state.rs similarity index 97% rename from src/core/raft_role/role_state.rs rename to d-engine-core/src/raft_role/role_state.rs index c9f6552a..de96940a 100644 --- a/src/core/raft_role/role_state.rs +++ b/d-engine-core/src/raft_role/role_state.rs @@ -1,7 +1,7 @@ use tokio::sync::mpsc; use tokio::time::Instant; -use tonic::async_trait; use tonic::Status; +use tonic::async_trait; use tracing::debug; use tracing::error; use tracing::warn; @@ -9,12 +9,6 @@ use tracing::warn; use super::RaftRole; use super::SharedState; use super::StateSnapshot; -use crate::proto::common::EntryPayload; -use crate::proto::election::VotedFor; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::replication::AppendEntriesResponse; -use crate::scoped_timer::ScopedTimer; -use crate::utils::cluster::error; use crate::AppendResponseWithUpdates; use crate::MaybeCloneOneshotSender; use crate::Membership; @@ -30,6 +24,12 @@ use crate::Result; use crate::RoleEvent; use crate::StateTransitionError; use crate::TypeConfig; +use crate::scoped_timer::ScopedTimer; +use crate::utils::cluster::error; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::server::election::VotedFor; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::replication::AppendEntriesResponse; #[async_trait] pub(crate) trait RaftRoleState: Send + Sync + 'static { @@ -395,7 +395,9 @@ pub(crate) trait RaftRoleState: Send + Sync + 'static { // Conservatively fallback to a safe position, forcing the leader to retry or // trigger a snapshot. Return a Conflict response (conflict index = // current log length + 1) - error!("Replication failed. Conservatively fallback to a safe position, forcing the leader to retry"); + error!( + "Replication failed. Conservatively fallback to a safe position, forcing the leader to retry" + ); let response = AppendEntriesResponse::conflict( self.node_id(), my_term, diff --git a/src/core/raft_test.rs b/d-engine-core/src/raft_test.rs similarity index 99% rename from src/core/raft_test.rs rename to d-engine-core/src/raft_test.rs index bd4d129b..cf462cff 100644 --- a/src/core/raft_test.rs +++ b/d-engine-core/src/raft_test.rs @@ -9,18 +9,6 @@ use tokio::time::timeout; use tracing_test::traced_test; use super::*; -use crate::candidate_state::CandidateState; -use crate::cluster::is_candidate; -use crate::cluster::is_follower; -use crate::cluster::is_leader; -use crate::cluster::is_learner; -use crate::leader_state::LeaderState; -use crate::proto::cluster::MetadataRequest; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::NodeStatus; -use crate::proto::election::VoteResponse; -use crate::test_utils::mock_raft; -use crate::test_utils::MockTypeConfig; use crate::AppendResults; use crate::ConsensusError; use crate::ElectionError; @@ -33,6 +21,18 @@ use crate::MockTransport; use crate::PeerUpdate; use crate::RaftOneshot; use crate::VoteResult; +use crate::candidate_state::CandidateState; +use crate::cluster::is_candidate; +use crate::cluster::is_follower; +use crate::cluster::is_leader; +use crate::cluster::is_learner; +use crate::leader_state::LeaderState; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::mock_raft; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::server::cluster::MetadataRequest; +use d_engine_proto::server::cluster::NodeMeta; +use d_engine_proto::server::election::VoteResponse; /// # Case 1: Tick has higher priority than role event #[tokio::test] @@ -396,13 +396,13 @@ async fn test_election_timeout_case4() { NodeMeta { id: peer1_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, NodeMeta { id: peer2_id, address: "http://127.0.0.1:55002".to_string(), - role: CANDIDATE, + role: Candidate, status: NodeStatus::Active.into(), }, ] @@ -730,7 +730,7 @@ async fn test_handle_role_event_state_update_case1_1() { raft.register_new_commit_listener(tx); raft.handle_role_event(RoleEvent::NotifyNewCommitIndex(NewCommitData { new_commit_index, - role: LEADER, + role: Leader, current_term: 1, })) .await @@ -760,7 +760,7 @@ async fn test_handle_role_event_state_update_case1_2() { raft.register_new_commit_listener(tx); raft.handle_role_event(RoleEvent::NotifyNewCommitIndex(NewCommitData { new_commit_index, - role: LEADER, + role: Leader, current_term: 1, })) .await @@ -800,7 +800,7 @@ async fn test_handle_role_event_state_update_case1_3_1() { raft.register_new_commit_listener(tx); raft.handle_role_event(RoleEvent::NotifyNewCommitIndex(NewCommitData { new_commit_index, - role: LEADER, + role: Leader, current_term: 1, })) .await @@ -831,7 +831,7 @@ async fn test_handle_role_event_state_update_case1_3_2() { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -912,7 +912,7 @@ async fn test_handle_role_event_state_update_case1_4() { raft.register_new_commit_listener(tx); raft.handle_role_event(RoleEvent::NotifyNewCommitIndex(NewCommitData { new_commit_index, - role: LEADER, + role: Leader, current_term: 1, })) .await diff --git a/src/core/replication/batch_buffer.rs b/d-engine-core/src/replication/batch_buffer.rs similarity index 100% rename from src/core/replication/batch_buffer.rs rename to d-engine-core/src/replication/batch_buffer.rs diff --git a/src/core/replication/batch_buffer_test.rs b/d-engine-core/src/replication/batch_buffer_test.rs similarity index 100% rename from src/core/replication/batch_buffer_test.rs rename to d-engine-core/src/replication/batch_buffer_test.rs diff --git a/src/core/replication/mod.rs b/d-engine-core/src/replication/mod.rs similarity index 95% rename from src/core/replication/mod.rs rename to d-engine-core/src/replication/mod.rs index da58e047..2159d655 100644 --- a/src/core/replication/mod.rs +++ b/d-engine-core/src/replication/mod.rs @@ -23,25 +23,25 @@ use std::sync::Arc; use dashmap::DashMap; #[cfg(test)] use mockall::automock; -use tonic::async_trait; use tonic::Status; +use tonic::async_trait; use super::LeaderStateSnapshot; use super::StateSnapshot; -use crate::alias::ROF; -use crate::proto::client::ClientResponse; -use crate::proto::common::Entry; -use crate::proto::common::EntryPayload; -use crate::proto::common::LogId; -use crate::proto::replication::append_entries_response; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::replication::AppendEntriesResponse; -use crate::proto::replication::ConflictResult; -use crate::proto::replication::SuccessResult; use crate::AppendResults; use crate::MaybeCloneOneshotSender; use crate::Result; use crate::TypeConfig; +use crate::alias::ROF; +use d_engine_proto::client::ClientResponse; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::LogId; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::replication::ConflictResult; +use d_engine_proto::server::replication::SuccessResult; +use d_engine_proto::server::replication::append_entries_response; /// Request with response channel that can handle all Raft payload types #[derive(Debug)] diff --git a/src/core/replication/replication_handler.rs b/d-engine-core/src/replication/replication_handler.rs similarity index 97% rename from src/core/replication/replication_handler.rs rename to d-engine-core/src/replication/replication_handler.rs index 506aa138..931a4d3d 100644 --- a/src/core/replication/replication_handler.rs +++ b/d-engine-core/src/replication/replication_handler.rs @@ -16,20 +16,6 @@ use tracing::warn; use super::AppendResponseWithUpdates; use super::ReplicationCore; -use crate::alias::ROF; -use crate::proto::client::WriteCommand; -use crate::proto::common::entry_payload::Payload; -use crate::proto::common::Entry; -use crate::proto::common::EntryPayload; -use crate::proto::common::LogId; -use crate::proto::common::NodeStatus; -use crate::proto::replication::append_entries_response; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::replication::AppendEntriesResponse; -use crate::proto::replication::ConflictResult; -use crate::proto::replication::SuccessResult; -use crate::scoped_timer::ScopedTimer; -use crate::utils::cluster::is_majority; use crate::AppendResults; use crate::IdAllocationError; use crate::LeaderStateSnapshot; @@ -42,6 +28,20 @@ use crate::Result; use crate::StateSnapshot; use crate::Transport; use crate::TypeConfig; +use crate::alias::ROF; +use crate::scoped_timer::ScopedTimer; +use crate::utils::cluster::is_majority; +use d_engine_proto::client::WriteCommand; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::common::entry_payload::Payload; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::replication::ConflictResult; +use d_engine_proto::server::replication::SuccessResult; +use d_engine_proto::server::replication::append_entries_response; #[derive(Clone)] pub struct ReplicationHandler @@ -90,8 +90,7 @@ where if !learners.is_empty() { trace!( "handle_raft_request_in_batch - voters: {:?}, learners: {:?}", - voters, - learners + voters, learners ); } diff --git a/src/core/replication/replication_handler_test.rs b/d-engine-core/src/replication/replication_handler_test.rs similarity index 97% rename from src/core/replication/replication_handler_test.rs rename to d-engine-core/src/replication/replication_handler_test.rs index 78b1bbab..f7ac00b3 100644 --- a/src/core/replication/replication_handler_test.rs +++ b/d-engine-core/src/replication/replication_handler_test.rs @@ -12,31 +12,11 @@ use tracing_test::traced_test; use super::ReplicationCore; use super::ReplicationData; use super::ReplicationHandler; -use crate::client_command_to_entry_payloads; -use crate::convert::safe_kv_bytes; -use crate::proto::client::write_command::Insert; -use crate::proto::client::write_command::Operation; -use crate::proto::client::WriteCommand; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::entry_payload::Payload; -use crate::proto::common::Entry; -use crate::proto::common::EntryPayload; -use crate::proto::common::LogId; -use crate::proto::common::NodeStatus; -use crate::proto::replication::append_entries_response; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::replication::AppendEntriesResponse; -use crate::proto::replication::ConflictResult; -use crate::proto::replication::SuccessResult; -use crate::test_utils::generate_insert_commands; -use crate::test_utils::mock_raft_context; -use crate::test_utils::setup_raft_components; -use crate::test_utils::simulate_insert_command; -use crate::test_utils::MockTypeConfig; use crate::AppendResult; use crate::ConsensusError; use crate::Error; use crate::FileStorageEngine; +use crate::Leader; use crate::LeaderStateSnapshot; use crate::MockMembership; use crate::MockRaftLog; @@ -50,9 +30,29 @@ use crate::ReplicationError; use crate::StateSnapshot; use crate::StorageError; use crate::SystemError; -use crate::FOLLOWER; -use crate::LEADER; -use crate::LEARNER; +use crate::client_command_to_entry_payloads; +use crate::convert::safe_kv_bytes; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::generate_insert_commands; +use crate::test_utils::mock_raft_context; +use crate::test_utils::setup_raft_components; +use crate::test_utils::simulate_insert_command; +use d_engine_proto::client::WriteCommand; +use d_engine_proto::client::write_command::Insert; +use d_engine_proto::client::write_command::Operation; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeRole::Learner; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::common::entry_payload::Payload; +use d_engine_proto::server::cluster::NodeMeta; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::replication::ConflictResult; +use d_engine_proto::server::replication::SuccessResult; +use d_engine_proto::server::replication::append_entries_response; /// # Case 1: The peer3's next_index is equal to /// the end of the leader's old log, @@ -521,9 +521,11 @@ fn test_stale_term() { leader_id: 2, }; - assert!(handler - .check_append_entries_request_is_legal(my_term, &request, &Arc::new(raft_log)) - .is_higher_term()); + assert!( + handler + .check_append_entries_request_is_legal(my_term, &request, &Arc::new(raft_log)) + .is_higher_term() + ); } /// # Case 1: follower local raft log length > prev_log_index @@ -622,9 +624,11 @@ fn test_virtual_log_handling() { leader_id: 2, }; - assert!(handler - .check_append_entries_request_is_legal(my_term, &request, &Arc::new(raft_log)) - .is_success()); + assert!( + handler + .check_append_entries_request_is_legal(my_term, &request, &Arc::new(raft_log)) + .is_success() + ); } #[test] fn test_virtual_log_with_non_empty_log() { @@ -861,8 +865,8 @@ mod handle_raft_request_in_batch_test { use super::*; use crate::convert::safe_kv_bytes; - use crate::test_utils::node_config; use crate::test_utils::MockBuilder; + use crate::test_utils::node_config; /// # Case 1: No peers found /// ## Validation Criteria @@ -884,7 +888,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 1, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::new(), @@ -924,7 +928,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 1, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::new(), @@ -937,7 +941,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -945,7 +949,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -1004,7 +1008,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 1, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::new(), @@ -1069,7 +1073,7 @@ mod handle_raft_request_in_batch_test { current_term: 2, // Leader's term is 2 voted_for: None, commit_index: 1, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from_iter(vec![(peer2_id, 3)]), @@ -1102,7 +1106,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -1138,7 +1142,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 1, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::new(), @@ -1170,7 +1174,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -1225,7 +1229,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(peer2_id, 6)]), @@ -1261,7 +1265,7 @@ mod handle_raft_request_in_batch_test { membership.expect_replication_peers().returning(move || { vec![NodeMeta { id: peer2_id, - role: FOLLOWER, + role: Follower, address: "".to_string(), status: NodeStatus::Active.into(), }] @@ -1334,7 +1338,7 @@ mod handle_raft_request_in_batch_test { current_term: 6, voted_for: None, commit_index: 10, - role: LEADER, + role: Leader, }; // Initial next_index values from test case description @@ -1422,7 +1426,7 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: id as u32, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), } }) @@ -1564,7 +1568,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(peer2_id, 6), (peer3_id, 6), (peer4_id, 6), (peer5_id, 6)]), @@ -1618,25 +1622,25 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, NodeMeta { id: peer3_id, address: "http://127.0.0.1:55002".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, NodeMeta { id: peer4_id, address: "http://127.0.0.1:55003".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, NodeMeta { id: peer5_id, address: "http://127.0.0.1:55004".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, ] @@ -1727,7 +1731,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(peer2_id, 6), (peer3_id, 6)]), @@ -1771,13 +1775,13 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, NodeMeta { id: peer3_id, address: "http://127.0.0.1:55002".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, ] @@ -1856,7 +1860,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(peer2_id, 6), (peer3_id, 6)]), @@ -1901,13 +1905,13 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, NodeMeta { id: peer3_id, address: "http://127.0.0.1:55002".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, ] @@ -1977,7 +1981,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(peer2_id, 6), (peer3_id, 6)]), @@ -2022,13 +2026,13 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, NodeMeta { id: peer3_id, address: "http://127.0.0.1:55002".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, ] @@ -2093,7 +2097,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(peer2_id, 6), (peer3_id, 6)]), @@ -2196,7 +2200,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(peer2_id, 6)]), @@ -2228,7 +2232,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -2279,13 +2283,13 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: voter_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, NodeMeta { id: learner_id, address: "http://127.0.0.1:55002".to_string(), - role: LEARNER, + role: Learner, status: NodeStatus::Joining.into(), }, ] @@ -2294,7 +2298,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: voter_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -2335,7 +2339,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(voter_id, 6), (learner_id, 6)]), @@ -2384,13 +2388,13 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: voter_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }, NodeMeta { id: pending_id, address: "http://127.0.0.1:55002".to_string(), - role: LEARNER, + role: Learner, status: NodeStatus::Syncing.into(), }, ] @@ -2399,7 +2403,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: voter_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), }] }); @@ -2440,7 +2444,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(voter_id, 6), (pending_id, 6)]), @@ -2488,13 +2492,13 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: learner1, address: "http://127.0.0.1:55001".to_string(), - role: LEARNER, + role: Learner, status: NodeStatus::Joining.into(), }, NodeMeta { id: learner2, address: "http://127.0.0.1:55002".to_string(), - role: LEARNER, + role: Learner, status: NodeStatus::Joining.into(), }, ] @@ -2537,7 +2541,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(learner1, 6), (learner2, 6)]), @@ -2599,7 +2603,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 1, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::new(), @@ -2642,19 +2646,19 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: voter_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active as i32, }, NodeMeta { id: joining_id, address: "http://127.0.0.1:55002".to_string(), - role: LEARNER, + role: Learner, status: NodeStatus::Joining as i32, }, NodeMeta { id: pending_id, address: "http://127.0.0.1:55003".to_string(), - role: LEARNER, + role: Learner, status: NodeStatus::Syncing as i32, }, ] @@ -2663,7 +2667,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: voter_id, address: "http://127.0.0.1:55001".to_string(), - role: FOLLOWER, + role: Follower, status: NodeStatus::Active as i32, }] }); @@ -2709,7 +2713,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 10, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(voter_id, 11), (joining_id, 11), (pending_id, 11)]), @@ -2755,14 +2759,14 @@ mod handle_raft_request_in_batch_test { vec![ NodeMeta { id: voter_id, - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), ..Default::default() }, NodeMeta { id: pending_id, status: NodeStatus::Joining as i32, - role: LEARNER, + role: Learner, ..Default::default() }, ] @@ -2770,7 +2774,7 @@ mod handle_raft_request_in_batch_test { membership.expect_voters().returning(move || { vec![NodeMeta { id: voter_id, - role: FOLLOWER, + role: Follower, status: NodeStatus::Active.into(), ..Default::default() }] @@ -2812,7 +2816,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(2, 6), (3, 6)]), @@ -2901,7 +2905,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: leader_commit_index, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(learner_id, learner_match_index + 1)]), @@ -2945,7 +2949,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: learner_id, address: "http://127.0.0.1:55001".to_string(), - role: LEARNER, + role: Learner, status: NodeStatus::Syncing.into(), }] }); @@ -2985,7 +2989,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 1, - role: LEADER, + role: Leader, }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(learner_id, 1 + 1)]), diff --git a/src/core/state_machine_handler/default_state_machine_handler.rs b/d-engine-core/src/state_machine_handler/default_state_machine_handler.rs similarity index 98% rename from src/core/state_machine_handler/default_state_machine_handler.rs rename to d-engine-core/src/state_machine_handler/default_state_machine_handler.rs index 8f4d7b77..c66b937f 100644 --- a/src/core/state_machine_handler/default_state_machine_handler.rs +++ b/d-engine-core/src/state_machine_handler/default_state_machine_handler.rs @@ -1,10 +1,10 @@ use std::ops::RangeInclusive; use std::path::Path; use std::path::PathBuf; +use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; -use std::sync::Arc; use std::time::Duration; use async_compression::tokio::bufread::GzipDecoder; @@ -16,20 +16,20 @@ use memmap2::Mmap; use memmap2::MmapOptions; use tempfile::tempdir; use tokio::fs; +use tokio::fs::File; use tokio::fs::remove_dir_all; use tokio::fs::remove_file; -use tokio::fs::File; use tokio::io::AsyncReadExt; use tokio::io::AsyncWriteExt; use tokio::io::BufReader; -use tokio::sync::mpsc; use tokio::sync::RwLock; -use tokio::time::timeout; +use tokio::sync::mpsc; use tokio::time::Instant; +use tokio::time::timeout; use tokio_stream::StreamExt; use tokio_tar::Archive; -use tonic::async_trait; use tonic::Streaming; +use tonic::async_trait; use tracing::debug; use tracing::error; use tracing::info; @@ -40,23 +40,6 @@ use super::SnapshotAssembler; use super::SnapshotContext; use super::SnapshotPolicy; use super::StateMachineHandler; -use crate::alias::ROF; -use crate::alias::SMOF; -use crate::alias::SNP; -use crate::constants::SNAPSHOT_DIR_PREFIX; -use crate::convert::classify_error; -use crate::file_io::validate_checksum; -use crate::file_io::validate_compressed_format; -use crate::proto::client::ClientResult; -use crate::proto::common::Entry; -use crate::proto::common::LogId; -use crate::proto::storage::snapshot_ack::ChunkStatus; -use crate::proto::storage::PurgeLogRequest; -use crate::proto::storage::PurgeLogResponse; -use crate::proto::storage::SnapshotAck; -use crate::proto::storage::SnapshotChunk; -use crate::proto::storage::SnapshotMetadata; -use crate::scoped_timer::ScopedTimer; use crate::NewCommitData; use crate::RaftLog; use crate::Result; @@ -67,6 +50,23 @@ use crate::SnapshotPathManager; use crate::StateMachine; use crate::StorageError; use crate::TypeConfig; +use crate::alias::ROF; +use crate::alias::SMOF; +use crate::alias::SNP; +use crate::constants::SNAPSHOT_DIR_PREFIX; +use crate::convert::classify_error; +use crate::file_io::validate_checksum; +use crate::file_io::validate_compressed_format; +use crate::scoped_timer::ScopedTimer; +use d_engine_proto::client::ClientResult; +use d_engine_proto::common::Entry; +use d_engine_proto::common::LogId; +use d_engine_proto::server::storage::PurgeLogRequest; +use d_engine_proto::server::storage::PurgeLogResponse; +use d_engine_proto::server::storage::SnapshotAck; +use d_engine_proto::server::storage::SnapshotChunk; +use d_engine_proto::server::storage::SnapshotMetadata; +use d_engine_proto::server::storage::snapshot_ack::ChunkStatus; /// Unified snapshot metadata with precomputed values #[derive(Debug, Clone)] @@ -170,9 +170,7 @@ where let last_index = chunk.last().map(|entry| entry.index); trace!( "[node-{}] apply_chunk::entry={:?} last_index: {:?}", - self.node_id, - &chunk, - last_index + self.node_id, &chunk, last_index ); let sm = self.state_machine.clone(); diff --git a/src/core/state_machine_handler/default_state_machine_handler_test.rs b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs similarity index 98% rename from src/core/state_machine_handler/default_state_machine_handler_test.rs rename to d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs index 94a70ae0..1695e748 100644 --- a/src/core/state_machine_handler/default_state_machine_handler_test.rs +++ b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs @@ -9,12 +9,12 @@ use std::time::Duration; use bytes::Bytes; use futures::StreamExt; -use mockall::predicate::eq; use mockall::Sequence; -use tempfile::tempdir; +use mockall::predicate::eq; use tempfile::TempDir; -use tokio::fs::create_dir_all; +use tempfile::tempdir; use tokio::fs::File; +use tokio::fs::create_dir_all; use tokio::io::AsyncWriteExt; use tokio::sync::mpsc; use tokio::sync::watch; @@ -25,25 +25,6 @@ use tracing_test::traced_test; use super::DefaultStateMachineHandler; use super::MockStateMachineHandler; use super::StateMachineHandler; -use crate::constants::SNAPSHOT_DIR_PREFIX; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::Entry; -use crate::proto::common::LogId; -use crate::proto::election::VotedFor; -use crate::proto::storage::snapshot_ack::ChunkStatus; -use crate::proto::storage::snapshot_service_client::SnapshotServiceClient; -use crate::proto::storage::PurgeLogRequest; -use crate::proto::storage::SnapshotAck; -use crate::proto::storage::SnapshotChunk; -use crate::proto::storage::SnapshotMetadata; -use crate::test_utils::crate_test_snapshot_stream; -use crate::test_utils::create_test_chunk; -use crate::test_utils::create_test_compressed_snapshot; -use crate::test_utils::node_config; -use crate::test_utils::snapshot_config; -use crate::test_utils::MockBuilder; -use crate::test_utils::MockTypeConfig; -use crate::test_utils::MOCK_STATE_MACHINE_HANDLER_PORT_BASE; use crate::AppendResults; use crate::ConsensusError; use crate::ElectionError; @@ -57,6 +38,25 @@ use crate::Node; use crate::SnapshotError; use crate::StateUpdate; use crate::StorageError; +use crate::constants::SNAPSHOT_DIR_PREFIX; +use crate::test_utils::MOCK_STATE_MACHINE_HANDLER_PORT_BASE; +use crate::test_utils::MockBuilder; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::crate_test_snapshot_stream; +use crate::test_utils::create_test_chunk; +use crate::test_utils::create_test_compressed_snapshot; +use crate::test_utils::node_config; +use crate::test_utils::snapshot_config; +use d_engine_proto::common::Entry; +use d_engine_proto::common::LogId; +use d_engine_proto::server::cluster::NodeMeta; +use d_engine_proto::server::election::VotedFor; +use d_engine_proto::server::storage::PurgeLogRequest; +use d_engine_proto::server::storage::SnapshotAck; +use d_engine_proto::server::storage::SnapshotChunk; +use d_engine_proto::server::storage::SnapshotMetadata; +use d_engine_proto::server::storage::snapshot_ack::ChunkStatus; +use d_engine_proto::server::storage::snapshot_service_client::SnapshotServiceClient; // Case 1: normal update #[test] @@ -727,8 +727,8 @@ async fn test_apply_snapshot_stream_from_leader_case7() { } mod create_snapshot_tests { use super::*; + use crate::Leader; use crate::NewCommitData; - use crate::LEADER; /// # Case 1: Basic creation flow #[tokio::test] async fn test_create_snapshot_case1() { @@ -781,10 +781,12 @@ mod create_snapshot_tests { debug!(?final_path); assert!(final_path.is_file()); assert!(final_path.extension().unwrap() == "gz"); - assert!(final_path - .to_str() - .unwrap() - .contains(&format!("{SNAPSHOT_DIR_PREFIX}5-1.tar.gz",))); + assert!( + final_path + .to_str() + .unwrap() + .contains(&format!("{SNAPSHOT_DIR_PREFIX}5-1.tar.gz",)) + ); assert_eq!(metadata.last_included, Some(LogId { term: 1, index: 5 })); } @@ -872,7 +874,7 @@ mod create_snapshot_tests { // Verify flag is reset regardless of task outcome let ctx = NewCommitData { - role: LEADER, + role: Leader, current_term: 1, new_commit_index: 100, }; diff --git a/src/core/state_machine_handler/mod.rs b/d-engine-core/src/state_machine_handler/mod.rs similarity index 95% rename from src/core/state_machine_handler/mod.rs rename to d-engine-core/src/state_machine_handler/mod.rs index 5eda62fb..f05fa5b9 100644 --- a/src/core/state_machine_handler/mod.rs +++ b/d-engine-core/src/state_machine_handler/mod.rs @@ -54,15 +54,15 @@ use mockall::automock; use tonic::async_trait; use super::NewCommitData; -use crate::alias::ROF; -use crate::proto::client::ClientResult; -use crate::proto::common::LogId; -use crate::proto::storage::PurgeLogRequest; -use crate::proto::storage::PurgeLogResponse; -use crate::proto::storage::SnapshotChunk; -use crate::proto::storage::SnapshotMetadata; use crate::Result; use crate::TypeConfig; +use crate::alias::ROF; +use d_engine_proto::client::ClientResult; +use d_engine_proto::common::LogId; +use d_engine_proto::server::storage::PurgeLogRequest; +use d_engine_proto::server::storage::PurgeLogResponse; +use d_engine_proto::server::storage::SnapshotChunk; +use d_engine_proto::server::storage::SnapshotMetadata; #[cfg_attr(test, automock)] #[async_trait] diff --git a/src/core/state_machine_handler/snapshot_assembler.rs b/d-engine-core/src/state_machine_handler/snapshot_assembler.rs similarity index 99% rename from src/core/state_machine_handler/snapshot_assembler.rs rename to d-engine-core/src/state_machine_handler/snapshot_assembler.rs index 9a376550..c1421ae0 100644 --- a/src/core/state_machine_handler/snapshot_assembler.rs +++ b/d-engine-core/src/state_machine_handler/snapshot_assembler.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; +use std::sync::Arc; use std::sync::atomic::AtomicU32; use std::sync::atomic::Ordering; -use std::sync::Arc; use std::time::SystemTime; use std::time::UNIX_EPOCH; @@ -12,12 +12,12 @@ use tracing::debug; use tracing::trace; use tracing::warn; -use crate::file_io::create_parent_dir_if_not_exist; -use crate::proto::storage::SnapshotMetadata; use crate::Result; use crate::SnapshotError; use crate::SnapshotPathManager; use crate::StorageError; +use crate::file_io::create_parent_dir_if_not_exist; +use d_engine_proto::server::storage::SnapshotMetadata; pub(crate) struct SnapshotAssembler { temp_file: File, diff --git a/src/core/state_machine_handler/snapshot_assembler_test.rs b/d-engine-core/src/state_machine_handler/snapshot_assembler_test.rs similarity index 98% rename from src/core/state_machine_handler/snapshot_assembler_test.rs rename to d-engine-core/src/state_machine_handler/snapshot_assembler_test.rs index 5a28f056..605884fb 100644 --- a/src/core/state_machine_handler/snapshot_assembler_test.rs +++ b/d-engine-core/src/state_machine_handler/snapshot_assembler_test.rs @@ -5,10 +5,10 @@ use tempfile::tempdir; use tokio::fs::read; use tracing_test::traced_test; -use crate::proto::common::LogId; -use crate::proto::storage::SnapshotMetadata; use crate::SnapshotAssembler; use crate::SnapshotPathManager; +use d_engine_proto::common::LogId; +use d_engine_proto::server::storage::SnapshotMetadata; #[tokio::test] #[traced_test] diff --git a/src/core/state_machine_handler/snapshot_guard.rs b/d-engine-core/src/state_machine_handler/snapshot_guard.rs similarity index 100% rename from src/core/state_machine_handler/snapshot_guard.rs rename to d-engine-core/src/state_machine_handler/snapshot_guard.rs diff --git a/src/core/state_machine_handler/snapshot_policy/composite.rs b/d-engine-core/src/state_machine_handler/snapshot_policy/composite.rs similarity index 100% rename from src/core/state_machine_handler/snapshot_policy/composite.rs rename to d-engine-core/src/state_machine_handler/snapshot_policy/composite.rs diff --git a/src/core/state_machine_handler/snapshot_policy/composite_test.rs b/d-engine-core/src/state_machine_handler/snapshot_policy/composite_test.rs similarity index 96% rename from src/core/state_machine_handler/snapshot_policy/composite_test.rs rename to d-engine-core/src/state_machine_handler/snapshot_policy/composite_test.rs index cae7ba09..958d6d61 100644 --- a/src/core/state_machine_handler/snapshot_policy/composite_test.rs +++ b/d-engine-core/src/state_machine_handler/snapshot_policy/composite_test.rs @@ -3,14 +3,14 @@ use std::time::Duration; use tracing_test::traced_test; use super::*; +use crate::Leader; use crate::core::state_machine_handler::snapshot_policy::SnapshotContext; use crate::core::state_machine_handler::snapshot_policy::SnapshotPolicy; -use crate::proto::common::LogId; -use crate::FOLLOWER; -use crate::LEADER; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Follower; -const LEADER_ROLE: i32 = LEADER; -const FOLLOWER_ROLE: i32 = FOLLOWER; +const LEADER_ROLE: i32 = Leader; +const FOLLOWER_ROLE: i32 = Follower; /// Creates a leader context for testing fn leader_ctx(last_applied_index: u64) -> SnapshotContext { diff --git a/src/core/state_machine_handler/snapshot_policy/log_size.rs b/d-engine-core/src/state_machine_handler/snapshot_policy/log_size.rs similarity index 100% rename from src/core/state_machine_handler/snapshot_policy/log_size.rs rename to d-engine-core/src/state_machine_handler/snapshot_policy/log_size.rs diff --git a/src/core/state_machine_handler/snapshot_policy/log_size_test.rs b/d-engine-core/src/state_machine_handler/snapshot_policy/log_size_test.rs similarity index 86% rename from src/core/state_machine_handler/snapshot_policy/log_size_test.rs rename to d-engine-core/src/state_machine_handler/snapshot_policy/log_size_test.rs index 068602c4..a552b152 100644 --- a/src/core/state_machine_handler/snapshot_policy/log_size_test.rs +++ b/d-engine-core/src/state_machine_handler/snapshot_policy/log_size_test.rs @@ -3,11 +3,11 @@ use std::time::Duration; use std::time::Instant; use super::SnapshotContext; -use crate::proto::common::LogId; +use crate::Leader; use crate::LogSizePolicy; use crate::SnapshotPolicy; -use crate::FOLLOWER; -use crate::LEADER; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Follower; // Test context builder for better test readability fn test_context( @@ -32,28 +32,28 @@ fn test_context( #[test] fn triggers_when_log_size_exceeds_threshold() { let policy = LogSizePolicy::new(1000, Duration::from_secs(1)); - let ctx = test_context(1500, 500, LEADER); + let ctx = test_context(1500, 500, Leader); assert!(policy.should_trigger(&ctx)); } #[test] fn does_not_trigger_below_threshold() { let policy = LogSizePolicy::new(1000, Duration::from_secs(1)); - let ctx = test_context(1499, 500, LEADER); + let ctx = test_context(1499, 500, Leader); assert!(!policy.should_trigger(&ctx)); } #[test] fn triggers_at_exact_threshold() { let policy = LogSizePolicy::new(1000, Duration::from_secs(1)); - let ctx = test_context(1500, 500, LEADER); + let ctx = test_context(1500, 500, Leader); assert!(policy.should_trigger(&ctx)); } #[test] fn respects_cooldown_period() { let policy = LogSizePolicy::new(100, Duration::from_secs(1)); - let mut ctx = test_context(200, 100, LEADER); + let mut ctx = test_context(200, 100, Leader); // Initial check should trigger assert!(policy.should_trigger(&ctx)); @@ -66,7 +66,7 @@ fn respects_cooldown_period() { #[test] fn resets_after_cooldown_period() { let policy = LogSizePolicy::new(100, Duration::from_millis(100)); - let ctx = test_context(200, 100, LEADER); + let ctx = test_context(200, 100, Leader); // First trigger assert!(policy.should_trigger(&ctx)); @@ -79,7 +79,7 @@ fn resets_after_cooldown_period() { #[test] fn handles_concurrent_checks_with_cooldown() { let policy = Arc::new(LogSizePolicy::new(100, Duration::from_secs(1))); - let ctx = test_context(200, 100, LEADER); + let ctx = test_context(200, 100, Leader); let handles: Vec<_> = (0..10) .map(|_| { @@ -96,14 +96,14 @@ fn handles_concurrent_checks_with_cooldown() { #[test] fn non_leader_never_triggers() { let policy = LogSizePolicy::new(100, Duration::ZERO); - let ctx = test_context(200, 100, FOLLOWER); + let ctx = test_context(200, 100, Follower); assert!(!policy.should_trigger(&ctx)); } #[test] fn dynamic_threshold_adjustment() { let policy = LogSizePolicy::new(1000, Duration::from_secs(1)); - let ctx = test_context(1200, 500, LEADER); + let ctx = test_context(1200, 500, Leader); // Initial threshold not met assert!(!policy.should_trigger(&ctx)); @@ -126,7 +126,7 @@ fn handles_term_regression() { term: 3, }, // Higher than current term current_term: 2, - role: LEADER, + role: Leader, }; assert!(!policy.should_trigger(&ctx)); @@ -135,7 +135,7 @@ fn handles_term_regression() { #[test] fn high_frequency_performance() { let policy = LogSizePolicy::new(1000, Duration::from_millis(100)); - let ctx = test_context(1500, 500, LEADER); + let ctx = test_context(1500, 500, Leader); let start = Instant::now(); let mut trigger_count = 0; diff --git a/src/core/state_machine_handler/snapshot_policy/mod.rs b/d-engine-core/src/state_machine_handler/snapshot_policy/mod.rs similarity index 96% rename from src/core/state_machine_handler/snapshot_policy/mod.rs rename to d-engine-core/src/state_machine_handler/snapshot_policy/mod.rs index 6c918094..d4252396 100644 --- a/src/core/state_machine_handler/snapshot_policy/mod.rs +++ b/d-engine-core/src/state_machine_handler/snapshot_policy/mod.rs @@ -11,7 +11,7 @@ pub(crate) use log_size::*; pub(crate) use time_based::*; use crate::cluster::is_leader; -use crate::proto::common::LogId; +use d_engine_proto::common::LogId; #[cfg(test)] mod composite_test; diff --git a/src/core/state_machine_handler/snapshot_policy/time_based.rs b/d-engine-core/src/state_machine_handler/snapshot_policy/time_based.rs similarity index 100% rename from src/core/state_machine_handler/snapshot_policy/time_based.rs rename to d-engine-core/src/state_machine_handler/snapshot_policy/time_based.rs diff --git a/src/core/state_machine_handler/snapshot_policy/time_based_test.rs b/d-engine-core/src/state_machine_handler/snapshot_policy/time_based_test.rs similarity index 93% rename from src/core/state_machine_handler/snapshot_policy/time_based_test.rs rename to d-engine-core/src/state_machine_handler/snapshot_policy/time_based_test.rs index fe2b7690..a3d17b45 100644 --- a/src/core/state_machine_handler/snapshot_policy/time_based_test.rs +++ b/d-engine-core/src/state_machine_handler/snapshot_policy/time_based_test.rs @@ -4,10 +4,10 @@ use tokio::time; use tracing_test::traced_test; use super::*; +use crate::Leader; use crate::core::state_machine_handler::snapshot_policy::SnapshotContext; use crate::core::state_machine_handler::snapshot_policy::SnapshotPolicy; -use crate::proto::common::LogId; -use crate::LEADER; +use d_engine_proto::common::LogId; #[tokio::test] #[traced_test] @@ -19,7 +19,7 @@ async fn test_should_trigger_after_interval() { // Create context (leader) let ctx = SnapshotContext { - role: LEADER, // Assuming LEADER is defined elsewhere + role: Leader, // Assuming Leader is defined elsewhere last_included: LogId { term: 1, index: 1 }, last_applied: LogId { term: 1, index: 10 }, current_term: 1, @@ -45,7 +45,7 @@ async fn test_should_not_trigger_before_interval() { // Create context (leader) let ctx = SnapshotContext { - role: LEADER, + role: Leader, last_included: LogId { term: 1, index: 1 }, last_applied: LogId { term: 1, index: 10 }, current_term: 1, @@ -67,7 +67,7 @@ async fn test_reset_timer_works() { policy.reset_timer(); let ctx = SnapshotContext { - role: LEADER, + role: Leader, last_included: LogId { term: 1, index: 1 }, last_applied: LogId { term: 1, index: 10 }, current_term: 1, diff --git a/src/core/state_machine_handler/snapshot_stream_processor.rs b/d-engine-core/src/state_machine_handler/snapshot_stream_processor.rs similarity index 100% rename from src/core/state_machine_handler/snapshot_stream_processor.rs rename to d-engine-core/src/state_machine_handler/snapshot_stream_processor.rs diff --git a/src/storage/buffered/buffered_raft_log.rs b/d-engine-core/src/storage/buffered/buffered_raft_log.rs similarity index 100% rename from src/storage/buffered/buffered_raft_log.rs rename to d-engine-core/src/storage/buffered/buffered_raft_log.rs diff --git a/src/storage/buffered/buffered_raft_log_test.rs b/d-engine-core/src/storage/buffered/buffered_raft_log_test.rs similarity index 100% rename from src/storage/buffered/buffered_raft_log_test.rs rename to d-engine-core/src/storage/buffered/buffered_raft_log_test.rs diff --git a/src/storage/buffered/mod.rs b/d-engine-core/src/storage/buffered/mod.rs similarity index 100% rename from src/storage/buffered/mod.rs rename to d-engine-core/src/storage/buffered/mod.rs diff --git a/d-engine-core/src/storage/mod.rs b/d-engine-core/src/storage/mod.rs new file mode 100644 index 00000000..b68a3cf9 --- /dev/null +++ b/d-engine-core/src/storage/mod.rs @@ -0,0 +1,16 @@ +mod buffered; +mod raft_log; +mod snapshot_path_manager; +mod state_machine; +mod storage_engine; + +pub(crate) use snapshot_path_manager::*; +pub use state_machine::*; +pub use storage_engine::*; + +#[doc(hidden)] +pub use raft_log::*; +#[doc(hidden)] +pub mod state_machine_test; +#[doc(hidden)] +pub mod storage_engine_test; diff --git a/src/storage/raft_log.rs b/d-engine-core/src/storage/raft_log.rs similarity index 100% rename from src/storage/raft_log.rs rename to d-engine-core/src/storage/raft_log.rs diff --git a/src/storage/snapshot_path_manager.rs b/d-engine-core/src/storage/snapshot_path_manager.rs similarity index 100% rename from src/storage/snapshot_path_manager.rs rename to d-engine-core/src/storage/snapshot_path_manager.rs diff --git a/src/storage/state_machine.rs b/d-engine-core/src/storage/state_machine.rs similarity index 100% rename from src/storage/state_machine.rs rename to d-engine-core/src/storage/state_machine.rs diff --git a/src/storage/state_machine_test.rs b/d-engine-core/src/storage/state_machine_test.rs similarity index 100% rename from src/storage/state_machine_test.rs rename to d-engine-core/src/storage/state_machine_test.rs diff --git a/src/storage/storage_engine.rs b/d-engine-core/src/storage/storage_engine.rs similarity index 100% rename from src/storage/storage_engine.rs rename to d-engine-core/src/storage/storage_engine.rs diff --git a/src/storage/storage_engine_test.rs b/d-engine-core/src/storage/storage_engine_test.rs similarity index 100% rename from src/storage/storage_engine_test.rs rename to d-engine-core/src/storage/storage_engine_test.rs diff --git a/src/storage/storage_test.rs b/d-engine-core/src/storage/storage_test.rs similarity index 100% rename from src/storage/storage_test.rs rename to d-engine-core/src/storage/storage_test.rs diff --git a/src/core/timer/election_timer.rs b/d-engine-core/src/timer/election_timer.rs similarity index 100% rename from src/core/timer/election_timer.rs rename to d-engine-core/src/timer/election_timer.rs diff --git a/src/core/timer/mod.rs b/d-engine-core/src/timer/mod.rs similarity index 100% rename from src/core/timer/mod.rs rename to d-engine-core/src/timer/mod.rs diff --git a/src/core/timer/replication_timer.rs b/d-engine-core/src/timer/replication_timer.rs similarity index 100% rename from src/core/timer/replication_timer.rs rename to d-engine-core/src/timer/replication_timer.rs diff --git a/src/type_config.rs b/d-engine-core/src/type_config.rs similarity index 100% rename from src/type_config.rs rename to d-engine-core/src/type_config.rs diff --git a/d-engine-core/src/utils/cluster.rs b/d-engine-core/src/utils/cluster.rs new file mode 100644 index 00000000..a2ab7170 --- /dev/null +++ b/d-engine-core/src/utils/cluster.rs @@ -0,0 +1,83 @@ +use std::sync::Arc; +use std::time::SystemTime; +use std::time::UNIX_EPOCH; + +use dashmap::DashSet; +use metrics::gauge; +use tracing::error; + +use d_engine_proto::common::Entry; +use d_engine_proto::common::NodeRole::Candidate; +use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeRole::Leader; +use d_engine_proto::common::NodeRole::Learner; + +pub(crate) fn collect_ids(entries: &[Entry]) -> Vec { + entries.iter().map(|e| e.index).collect() +} + +pub(crate) fn is_majority( + num: usize, + total: usize, +) -> bool { + num > (total / 2) +} + +pub fn majority_count(total_nodes: usize) -> usize { + (total_nodes / 2) + 1 +} + +pub(crate) fn find_nearest_lower_number( + target_index: u64, + set_of_index: Arc>, +) -> Option { + set_of_index + .iter() + .filter(|index| **index <= target_index) // Filter only numbers <= learner_next_index + .max_by_key(|index| **index) // Find the maximum of the filtered numbers + .map(|index| *index) // Convert from reference to value +} + +/// record down cluster level error for debug and code optimization purpose. +pub(crate) fn record_down_cluster_error(event_id: u64) { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs_f64(); + + let event_type = event_id.to_string(); + + gauge!( + "cluster_fatal_error_metric", + "event_type" => event_type + ) + .set(timestamp); +} + +/// Format error logging +pub fn error( + func_name: &str, + e: &dyn std::fmt::Debug, +) { + error!("{}::{} failed: {:?}", module_path!(), func_name, e); +} + +#[inline] +pub fn is_follower(role_i32: i32) -> bool { + role_i32 == Follower +} + +#[inline] +pub fn is_candidate(role_i32: i32) -> bool { + role_i32 == Candidate +} + +#[inline] +pub fn is_leader(role_i32: i32) -> bool { + role_i32 == Leader +} + +#[inline] +pub fn is_learner(role_i32: i32) -> bool { + role_i32 == Learner +} diff --git a/d-engine-core/src/utils/mod.rs b/d-engine-core/src/utils/mod.rs new file mode 100644 index 00000000..daed9263 --- /dev/null +++ b/d-engine-core/src/utils/mod.rs @@ -0,0 +1,4 @@ +mod cluster; +mod scoped_timer; + +pub use scoped_timer::ScopedTimer; diff --git a/d-engine-core/src/utils/scoped_timer.rs b/d-engine-core/src/utils/scoped_timer.rs new file mode 100644 index 00000000..d6ee05c3 --- /dev/null +++ b/d-engine-core/src/utils/scoped_timer.rs @@ -0,0 +1,23 @@ +use tokio::time::Instant; +use tracing::debug; + +pub struct ScopedTimer { + start: Instant, + name: &'static str, +} + +impl ScopedTimer { + pub fn new(name: &'static str) -> Self { + Self { + start: Instant::now(), + name, + } + } +} + +impl Drop for ScopedTimer { + fn drop(&mut self) { + let elapsed = self.start.elapsed(); + debug!(target: "timing", "[TIMING] {} took {} ms", self.name, elapsed.as_millis()); + } +} diff --git a/d-engine-docs/Cargo.toml b/d-engine-docs/Cargo.toml new file mode 100644 index 00000000..58fca17e --- /dev/null +++ b/d-engine-docs/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "d-engine-docs" +version = "0.2.0" +edition = "2024" + +[dependencies] diff --git a/src/docs/architecture/election-design.md b/d-engine-docs/src/docs/architecture/election-design.md similarity index 100% rename from src/docs/architecture/election-design.md rename to d-engine-docs/src/docs/architecture/election-design.md diff --git a/src/docs/architecture/error-handling-design-principles.md b/d-engine-docs/src/docs/architecture/error-handling-design-principles.md similarity index 100% rename from src/docs/architecture/error-handling-design-principles.md rename to d-engine-docs/src/docs/architecture/error-handling-design-principles.md diff --git a/src/docs/architecture/mod.rs b/d-engine-docs/src/docs/architecture/mod.rs similarity index 100% rename from src/docs/architecture/mod.rs rename to d-engine-docs/src/docs/architecture/mod.rs diff --git a/src/docs/architecture/new-node-join-architecture.md b/d-engine-docs/src/docs/architecture/new-node-join-architecture.md similarity index 100% rename from src/docs/architecture/new-node-join-architecture.md rename to d-engine-docs/src/docs/architecture/new-node-join-architecture.md diff --git a/src/docs/architecture/raft-log-persistence-architecture.md b/d-engine-docs/src/docs/architecture/raft-log-persistence-architecture.md similarity index 100% rename from src/docs/architecture/raft-log-persistence-architecture.md rename to d-engine-docs/src/docs/architecture/raft-log-persistence-architecture.md diff --git a/src/docs/architecture/raft-role.md b/d-engine-docs/src/docs/architecture/raft-role.md similarity index 100% rename from src/docs/architecture/raft-role.md rename to d-engine-docs/src/docs/architecture/raft-role.md diff --git a/src/docs/architecture/single-responsibility-principle.md b/d-engine-docs/src/docs/architecture/single-responsibility-principle.md similarity index 100% rename from src/docs/architecture/single-responsibility-principle.md rename to d-engine-docs/src/docs/architecture/single-responsibility-principle.md diff --git a/src/docs/architecture/snapshot-module-design.md b/d-engine-docs/src/docs/architecture/snapshot-module-design.md similarity index 100% rename from src/docs/architecture/snapshot-module-design.md rename to d-engine-docs/src/docs/architecture/snapshot-module-design.md diff --git a/src/docs/client_guide/mod.rs b/d-engine-docs/src/docs/client_guide/mod.rs similarity index 100% rename from src/docs/client_guide/mod.rs rename to d-engine-docs/src/docs/client_guide/mod.rs diff --git a/src/docs/client_guide/read_consistency.md b/d-engine-docs/src/docs/client_guide/read_consistency.md similarity index 100% rename from src/docs/client_guide/read_consistency.md rename to d-engine-docs/src/docs/client_guide/read_consistency.md diff --git a/src/docs/mod.rs b/d-engine-docs/src/docs/mod.rs similarity index 100% rename from src/docs/mod.rs rename to d-engine-docs/src/docs/mod.rs diff --git a/src/docs/overview.md b/d-engine-docs/src/docs/overview.md similarity index 100% rename from src/docs/overview.md rename to d-engine-docs/src/docs/overview.md diff --git a/src/docs/performance/mod.rs b/d-engine-docs/src/docs/performance/mod.rs similarity index 100% rename from src/docs/performance/mod.rs rename to d-engine-docs/src/docs/performance/mod.rs diff --git a/src/docs/performance/throughput-optimization-guide.md b/d-engine-docs/src/docs/performance/throughput-optimization-guide.md similarity index 100% rename from src/docs/performance/throughput-optimization-guide.md rename to d-engine-docs/src/docs/performance/throughput-optimization-guide.md diff --git a/src/docs/quick-start-5min.md b/d-engine-docs/src/docs/quick-start-5min.md similarity index 100% rename from src/docs/quick-start-5min.md rename to d-engine-docs/src/docs/quick-start-5min.md diff --git a/src/docs/server_guide/customize-raft-log-purge-executor.md b/d-engine-docs/src/docs/server_guide/customize-raft-log-purge-executor.md similarity index 100% rename from src/docs/server_guide/customize-raft-log-purge-executor.md rename to d-engine-docs/src/docs/server_guide/customize-raft-log-purge-executor.md diff --git a/src/docs/server_guide/customize-state-machine.md b/d-engine-docs/src/docs/server_guide/customize-state-machine.md similarity index 100% rename from src/docs/server_guide/customize-state-machine.md rename to d-engine-docs/src/docs/server_guide/customize-state-machine.md diff --git a/src/docs/server_guide/customize-storage-engine.md b/d-engine-docs/src/docs/server_guide/customize-storage-engine.md similarity index 100% rename from src/docs/server_guide/customize-storage-engine.md rename to d-engine-docs/src/docs/server_guide/customize-storage-engine.md diff --git a/src/docs/server_guide/mod.rs b/d-engine-docs/src/docs/server_guide/mod.rs similarity index 100% rename from src/docs/server_guide/mod.rs rename to d-engine-docs/src/docs/server_guide/mod.rs diff --git a/d-engine-docs/src/lib.rs b/d-engine-docs/src/lib.rs new file mode 100644 index 00000000..ca422a9c --- /dev/null +++ b/d-engine-docs/src/lib.rs @@ -0,0 +1 @@ +pub mod docs; diff --git a/d-engine-proto/proto/common.proto b/d-engine-proto/proto/common.proto index 2e1b23c8..ddad4732 100644 --- a/d-engine-proto/proto/common.proto +++ b/d-engine-proto/proto/common.proto @@ -101,4 +101,5 @@ enum NodeRole { FOLLOWER = 0; CANDIDATE = 1; LEADER = 2; + LEARNER = 3; } diff --git a/d-engine-proto/src/generated/d_engine.common.rs b/d-engine-proto/src/generated/d_engine.common.rs index da105668..143b5c08 100644 --- a/d-engine-proto/src/generated/d_engine.common.rs +++ b/d-engine-proto/src/generated/d_engine.common.rs @@ -200,6 +200,7 @@ pub enum NodeRole { Follower = 0, Candidate = 1, Leader = 2, + Learner = 3, } impl NodeRole { /// String value of the enum field names used in the ProtoBuf definition. @@ -211,6 +212,7 @@ impl NodeRole { Self::Follower => "FOLLOWER", Self::Candidate => "CANDIDATE", Self::Leader => "LEADER", + Self::Learner => "LEARNER", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -219,6 +221,7 @@ impl NodeRole { "FOLLOWER" => Some(Self::Follower), "CANDIDATE" => Some(Self::Candidate), "LEADER" => Some(Self::Leader), + "LEARNER" => Some(Self::Learner), _ => None, } } diff --git a/d-engine-server/src/lib.rs b/d-engine-server/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/d-engine-server/src/lib.rs @@ -0,0 +1 @@ + diff --git a/d-engine-server/src/main.rs b/d-engine-server/src/main.rs deleted file mode 100644 index e7a11a96..00000000 --- a/d-engine-server/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/src/core/mod.rs b/src/core/mod.rs deleted file mode 100644 index e03ae20a..00000000 --- a/src/core/mod.rs +++ /dev/null @@ -1,103 +0,0 @@ -mod commit_handler; -mod election; -mod event; -mod purge; -mod raft; -mod raft_context; -mod raft_role; -mod replication; -mod state_machine_handler; -mod timer; - -use bytes::Bytes; -pub(crate) use commit_handler::*; -pub(crate) use election::*; -pub(crate) use event::*; -pub(crate) use purge::*; -pub(crate) use raft::*; -pub(crate) use raft_context::*; -#[doc(hidden)] -pub use raft_role::*; -pub(crate) use replication::*; -pub(crate) use state_machine_handler::*; -pub(crate) use timer::*; -use tracing::instrument; - -use crate::proto::common::entry_payload::Payload; -use crate::proto::common::membership_change::Change; -use crate::proto::common::EntryPayload; -use crate::proto::common::MembershipChange; -use crate::proto::common::Noop; - -#[cfg(test)] -mod raft_oneshot_test; -#[cfg(test)] -mod raft_test; - -/// In raft, during any Leader to Peer communication, -/// if received response term is bigger than Leader's, -/// the current Leader need downgrade to follower -/// and update its term to higher one. -/// -/// e.g. Append Entries RPC -/// e.g. Election: receive VoteResponse -/// e.g. Sync cluster membership configure -/// @return: true - found higher term; -pub(crate) fn if_higher_term_found( - my_current_term: u64, - term: u64, - is_learner: bool, -) -> bool { - //means I am fake leader or we should ignore learner's response - if !is_learner && my_current_term < term { - tracing::warn!("my_current_term: {} < term: {} ?", my_current_term, term); - return true; - } - - false -} - -/// Raft paper: 5.4.1 Election restriction -/// -/// Raft determines which of two logs is more up-to-date by comparing the index and term of the last -/// entries in the logs. If the logs have last entries with different terms, then the log with the -/// later term is more up-to-date. If the logs end with the same term, then whichever log is longer -/// is more up-to-date. -#[instrument] -pub(crate) fn is_target_log_more_recent( - my_last_log_index: u64, - my_last_log_term: u64, - target_last_log_index: u64, - target_last_log_term: u64, -) -> bool { - (target_last_log_term > my_last_log_term) - || (target_last_log_term == my_last_log_term && target_last_log_index >= my_last_log_index) -} - -#[derive(Debug, Clone, Copy)] -pub enum QuorumStatus { - Confirmed, // Confirmed by the majority of nodes - LostQuorum, // Unable to obtain majority - NetworkError, // Network problem (can be retried) -} - -impl EntryPayload { - pub fn command(command: Bytes) -> Self { - Self { - payload: Some(Payload::Command(command)), - } - } - pub fn noop() -> Self { - Self { - payload: Some(Payload::Noop(Noop {})), - } - } - - pub fn config(change: Change) -> Self { - Self { - payload: Some(Payload::Config(MembershipChange { - change: Some(change), - })), - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 8a74934c..c3c4f4c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,6 @@ mod core; mod errors; mod membership; mod network; -mod type_config; #[doc(hidden)] pub use core::*; @@ -34,8 +33,6 @@ pub use storage::*; #[doc(hidden)] pub mod utils; #[doc(hidden)] -pub use type_config::*; -#[doc(hidden)] pub use utils::*; //----------------------------------------------------------- diff --git a/src/membership/mod.rs b/src/membership/mod.rs index 5d50006d..91052c0d 100644 --- a/src/membership/mod.rs +++ b/src/membership/mod.rs @@ -7,304 +7,3 @@ pub use raft_membership::*; mod membership_guard_test; #[cfg(test)] mod raft_membership_test; - -#[cfg(test)] -use mockall::automock; -use tonic::async_trait; -use tonic::transport::Channel; - -use crate::proto::cluster::cluster_conf_update_response::ErrorCode; -use crate::proto::cluster::ClusterConfChangeRequest; -use crate::proto::cluster::ClusterConfUpdateResponse; -use crate::proto::cluster::ClusterMembership; -use crate::proto::common::NodeStatus; -use crate::Result; -use crate::TypeConfig; - -// Add connection type management in RpcPeerChannels -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub(crate) enum ConnectionType { - Control, // Used for key operations such as elections/heartbeats - Data, // Used for log replication - Bulk, // Used for high-traffic operations such as snapshot transmission -} -impl ConnectionType { - pub(crate) fn all() -> Vec { - vec![ - ConnectionType::Control, - ConnectionType::Data, - ConnectionType::Bulk, - ] - } -} - -#[cfg_attr(test, automock)] -#[async_trait] -pub trait Membership: Sync + Send + 'static -where - T: TypeConfig, -{ - /// All nodes (including itself) - #[allow(unused)] - async fn members(&self) -> Vec; - - /// All non-self nodes (including Syncing and Active) - /// Note: - /// Joining node has not start its Raft event processing engine yet. - async fn replication_peers(&self) -> Vec; - - /// All non-self nodes in Active state - async fn voters(&self) -> Vec; - - /// All pending active nodes in Active state - #[allow(unused)] - async fn nodes_with_status( - &self, - status: NodeStatus, - ) -> Vec; - - async fn get_node_status( - &self, - node_id: u32, - ) -> Option; - - async fn check_cluster_is_ready(&self) -> Result<()>; - - async fn get_peers_id_with_condition( - &self, - condition: F, - ) -> Vec - where - F: Fn(i32) -> bool + Send + Sync + 'static; - - async fn mark_leader_id( - &self, - leader_id: u32, - ) -> Result<()>; - - async fn current_leader_id(&self) -> Option; - - /// Reset old leader to follower - async fn reset_leader(&self) -> Result<()>; - - /// If node role not found return Error - async fn update_node_role( - &self, - node_id: u32, - new_role: i32, - ) -> Result<()>; - - /// retrieve latest cluster membership - async fn retrieve_cluster_membership_config(&self) -> ClusterMembership; - - /// invoked when receive requests from Leader - async fn update_cluster_conf_from_leader( - &self, - my_id: u32, - my_current_term: u64, - current_conf_version: u64, - current_leader_id: Option, - cluster_conf_change_req: &ClusterConfChangeRequest, - ) -> Result; - - async fn get_cluster_conf_version(&self) -> u64; - - async fn update_conf_version( - &self, - version: u64, - ); - - #[allow(unused)] - async fn incr_conf_version(&self); - - /// Add a new node as a learner - async fn add_learner( - &self, - node_id: u32, - address: String, - ) -> Result<()>; - - /// Activate node - #[allow(unused)] - async fn activate_node( - &mut self, - new_node_id: u32, - ) -> Result<()>; - - /// Update status of a node - async fn update_node_status( - &self, - node_id: u32, - status: NodeStatus, - ) -> Result<()>; - - /// Check if the node already exists - async fn contains_node( - &self, - node_id: u32, - ) -> bool; - - async fn retrieve_node_meta( - &self, - node_id: u32, - ) -> Option; - - /// Elegantly remove nodes - async fn remove_node( - &self, - node_id: u32, - ) -> Result<()>; - - /// Forcefully remove faulty nodes - #[allow(unused)] - async fn force_remove_node( - &self, - node_id: u32, - ) -> Result<()>; - - /// Get all node status - #[allow(unused)] - async fn get_all_nodes(&self) -> Vec; - - /// Pre-warms connection cache for all replication peers - async fn pre_warm_connections(&self) -> Result<()>; - - async fn get_peer_channel( - &self, - node_id: u32, - conn_type: ConnectionType, - ) -> Option; - - async fn get_address( - &self, - node_id: u32, - ) -> Option; - - /// Apply committed configuration change - async fn apply_config_change( - &self, - change: crate::proto::common::MembershipChange, - ) -> Result<()>; - - async fn notify_config_applied( - &self, - index: u64, - ); - - async fn get_zombie_candidates(&self) -> Vec; - - /// If new node could rejoin the cluster again - async fn can_rejoin( - &self, - node_id: u32, - role: i32, - ) -> Result<()>; -} - -impl ClusterConfUpdateResponse { - /// Generate a successful response (full success) - pub(crate) fn success( - node_id: u32, - term: u64, - version: u64, - ) -> Self { - Self { - id: node_id, - term, - version, - success: true, - error_code: ErrorCode::None.into(), - } - } - - /// Generate a failed response (Stale leader term) - pub(crate) fn higher_term( - node_id: u32, - term: u64, - version: u64, - ) -> Self { - Self { - id: node_id, - term, - version, - success: false, - error_code: ErrorCode::TermOutdated.into(), - } - } - - /// Generate a failed response (Request sent to non-leader or an out-dated leader) - pub(crate) fn not_leader( - node_id: u32, - term: u64, - version: u64, - ) -> Self { - Self { - id: node_id, - term, - version, - success: false, - error_code: ErrorCode::NotLeader.into(), - } - } - - /// Generate a failed response (Stale configuration version) - pub(crate) fn version_conflict( - node_id: u32, - term: u64, - version: u64, - ) -> Self { - Self { - id: node_id, - term, - version, - success: false, - error_code: ErrorCode::VersionConflict.into(), - } - } - - /// Generate a failed response (Malformed change request) - #[allow(unused)] - pub(crate) fn invalid_change( - node_id: u32, - term: u64, - version: u64, - ) -> Self { - Self { - id: node_id, - term, - version, - success: false, - error_code: ErrorCode::InvalidChange.into(), - } - } - - /// Generate a failed response (Server-side processing error) - pub(crate) fn internal_error( - node_id: u32, - term: u64, - version: u64, - ) -> Self { - Self { - id: node_id, - term, - version, - success: false, - error_code: ErrorCode::InternalError.into(), - } - } - - #[allow(unused)] - pub(crate) fn is_higher_term(&self) -> bool { - self.error_code == >::into(ErrorCode::TermOutdated) - } -} - -impl NodeStatus { - pub fn is_promotable(&self) -> bool { - matches!(self, NodeStatus::Syncing) - } - - pub fn is_i32_promotable(value: i32) -> bool { - matches!(value, v if v == (NodeStatus::Syncing as i32)) - } -} From 9195ce201ed319bdd467a5ad10ff65e68f806ca3 Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Mon, 20 Oct 2025 22:29:32 +0800 Subject: [PATCH 04/26] feat #167: continue working on d-engine-core --- Cargo.lock | 102 ++++- Cargo.toml | 8 +- d-engine-client/src/client_extensions.rs | 342 ---------------- d-engine-client/src/lib.rs | 3 +- d-engine-client/src/proto/client_ext.rs | 95 +++++ d-engine-client/src/proto/mod.rs | 120 ++++++ d-engine-core/Cargo.toml | 9 +- .../default_commit_handler_test.rs | 2 +- {src => d-engine-core/src}/config/cluster.rs | 8 +- .../src}/config/config_test.rs | 4 +- {src => d-engine-core/src}/config/mod.rs | 2 +- .../src}/config/monitoring.rs | 0 {src => d-engine-core/src}/config/network.rs | 0 .../src}/config/network_test.rs | 0 {src => d-engine-core/src}/config/raft.rs | 30 +- .../src}/config/raft_test.rs | 0 {src => d-engine-core/src}/config/retry.rs | 0 {src => d-engine-core/src}/config/security.rs | 0 {src => d-engine-core/src}/config/tls.rs | 0 {src => d-engine-core/src}/config/tls_test.rs | 0 d-engine-core/src/lib.rs | 33 +- .../src}/maybe_clone_oneshot.rs | 0 d-engine-core/src/network/mod.rs | 366 +++++++++++++++++ d-engine-core/src/purge/mod.rs | 2 +- .../src/raft_role/candidate_state.rs | 2 +- .../src/raft_role/candidate_state_test.rs | 2 +- d-engine-core/src/raft_role/follower_state.rs | 2 +- .../src/raft_role/leader_state_test.rs | 2 +- d-engine-core/src/raft_test.rs | 2 +- d-engine-core/src/replication/mod.rs | 70 ---- .../src/replication/replication_handler.rs | 2 +- .../src/state_machine_handler/mod.rs | 4 +- .../snapshot_policy/composite_test.rs | 6 +- .../src/storage/buffered/buffered_raft_log.rs | 26 +- .../buffered/buffered_raft_log_test.rs | 30 +- d-engine-core/src/storage/raft_log.rs | 8 +- .../src/storage/snapshot_path_manager.rs | 2 +- d-engine-core/src/storage/state_machine.rs | 6 +- .../src/storage/state_machine_test.rs | 20 +- d-engine-core/src/storage/storage_engine.rs | 4 +- .../src/storage/storage_engine_test.rs | 20 +- d-engine-core/src/storage/storage_test.rs | 10 +- d-engine-proto/src/exts/client_ext.rs | 170 ++++++++ .../src/exts/cluster_ext.rs | 81 +--- d-engine-proto/src/exts/common_ext.rs | 25 ++ d-engine-proto/src/exts/mod.rs | 9 + d-engine-proto/src/exts/replication_ext.rs | 76 ++++ d-engine-proto/src/lib.rs | 5 + d-engine-runtime/Cargo.toml | 16 + .../src/lib.rs | 0 d-engine-server/Cargo.toml | 7 - src/network/mod.rs | 370 ------------------ src/node/type_config/mod.rs | 2 - 53 files changed, 1117 insertions(+), 988 deletions(-) delete mode 100644 d-engine-client/src/client_extensions.rs create mode 100644 d-engine-client/src/proto/client_ext.rs create mode 100644 d-engine-client/src/proto/mod.rs rename {src => d-engine-core/src}/config/cluster.rs (96%) rename {src => d-engine-core/src}/config/config_test.rs (99%) rename {src => d-engine-core/src}/config/mod.rs (99%) rename {src => d-engine-core/src}/config/monitoring.rs (100%) rename {src => d-engine-core/src}/config/network.rs (100%) rename {src => d-engine-core/src}/config/network_test.rs (100%) rename {src => d-engine-core/src}/config/raft.rs (96%) rename {src => d-engine-core/src}/config/raft_test.rs (100%) rename {src => d-engine-core/src}/config/retry.rs (100%) rename {src => d-engine-core/src}/config/security.rs (100%) rename {src => d-engine-core/src}/config/tls.rs (100%) rename {src => d-engine-core/src}/config/tls_test.rs (100%) rename {src/node/type_config => d-engine-core/src}/maybe_clone_oneshot.rs (100%) create mode 100644 d-engine-core/src/network/mod.rs create mode 100644 d-engine-proto/src/exts/client_ext.rs rename d-engine-core/src/cluster_proto_extension.rs => d-engine-proto/src/exts/cluster_ext.rs (56%) create mode 100644 d-engine-proto/src/exts/common_ext.rs create mode 100644 d-engine-proto/src/exts/mod.rs create mode 100644 d-engine-proto/src/exts/replication_ext.rs create mode 100644 d-engine-runtime/Cargo.toml rename {d-engine-server => d-engine-runtime}/src/lib.rs (100%) delete mode 100644 d-engine-server/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index b86161be..411d8dec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,6 +169,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.72.0" @@ -293,6 +302,7 @@ dependencies = [ name = "d-engine-core" version = "0.1.0" dependencies = [ + "bincode", "bytes", "config", "d-engine-proto", @@ -303,6 +313,10 @@ dependencies = [ "nanoid", "prost", "serde", + "serial_test", + "temp-env", + "tempfile", + "thiserror", "tokio", "tonic", "tracing", @@ -328,9 +342,16 @@ dependencies = [ [[package]] name = "d-engine-server" -version = "0.1.0" +version = "0.2.0" dependencies = [ + "config", + "d-engine-proto", "rocksdb", + "serde", + "serial_test", + "temp-env", + "tempfile", + "tracing", ] [[package]] @@ -985,6 +1006,16 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + [[package]] name = "parking_lot_core" version = "0.9.12" @@ -1358,12 +1389,27 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + [[package]] name = "serde" version = "1.0.219" @@ -1393,6 +1439,31 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" +dependencies = [ + "futures", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1472,6 +1543,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +[[package]] +name = "temp-env" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96374855068f47402c3121c6eed88d29cb1de8f3ab27090e273e420bdabcf050" +dependencies = [ + "parking_lot", +] + [[package]] name = "tempfile" version = "3.21.0" @@ -1491,6 +1571,26 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.9" diff --git a/Cargo.toml b/Cargo.toml index 97696a84..fe1b3235 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,8 @@ members = [ "d-engine-proto", "d-engine-core", "d-engine-client", - "d-engine-server", "d-engine-docs", + "d-engine-runtime", + "d-engine-docs", ] resolver = "2" @@ -12,7 +13,7 @@ resolver = "2" d-engine-proto = { path = "./d-engine-proto" } d-engine-core = { path = "./d-engine-core" } d-engine-client = { path = "./d-engine-client" } -d-engine-server = { path = "./d-engine-server" } +d-engine-runtime = { path = "./d-engine-runtime" } # [package] # name = "d-engine" @@ -57,7 +58,6 @@ tokio = { version = "1.4", features = [ "fs", "sync", ] } -tokio-stream = "0.1.16" # use tokio_util::sync::CancellationToken; tokio-util = "0.7.11" @@ -84,8 +84,6 @@ async-stream = "0.3.6" # Compress/decompress data stream async-compression = { version = "0.4", features = ["tokio", "gzip"] } -# Packing/unpacking TAR archives -tokio-tar = "0.3" http-body = "1.0" http-body-util = "0.1.3" memmap2 = "0.9.5" diff --git a/d-engine-client/src/client_extensions.rs b/d-engine-client/src/client_extensions.rs deleted file mode 100644 index e50de997..00000000 --- a/d-engine-client/src/client_extensions.rs +++ /dev/null @@ -1,342 +0,0 @@ -//! Extensions for protobuf-generated client types -//! -//! This module provides additional methods for protobuf-generated types -//! that are not automatically generated by the protobuf compiler. - -use bytes::Bytes; -use d_engine_proto::client::ClientReadRequest; -use d_engine_proto::client::ClientResult; -use d_engine_proto::client::client_response::SuccessResult; -use d_engine_proto::client::write_command; -use tracing::error; - -use crate::ClientApiError; -use d_engine_proto::client::ClientResponse; -use d_engine_proto::client::ReadConsistencyPolicy; -use d_engine_proto::client::ReadResults; -use d_engine_proto::client::WriteCommand; -use d_engine_proto::error::ErrorCode; - -pub trait ClientReadRequestExt { - /// Checks if the consistency_policy field is present in the request - /// - /// Returns true if the client explicitly specified a consistency policy, - /// false if the field is absent (should use server default). - #[allow(unused)] - fn has_consistency_policy(&self) -> bool; - - /// Gets the consistency policy value safely - /// - /// Returns Some(policy) if present, None if field is absent. - /// Safer alternative that doesn't panic. - #[allow(unused)] - fn get_consistency_policy(&self) -> Option; -} - -/// Extension trait for ClientResponse providing utility methods -/// -/// This trait adds client-side functionality to the protobuf-generated -/// ClientResponse type, making it easier to work with responses. -pub trait ClientResponseExt { - /// Build success response for write operations - /// - /// # Returns - /// Response with NoError code and write confirmation - #[allow(unused)] - fn write_success() -> ClientResponse; - - /// Check if the write operation was successful - /// - /// # Returns - /// - `true` if the response indicates a successful write operation - /// - `false` if the response indicates a failed write operation or is not a write response - #[allow(unused)] - fn is_write_success(&self) -> bool; - - /// Build success response for read operations - /// - /// # Parameters - /// - `results`: Vector of retrieved key-value pairs - #[allow(unused)] - fn read_results(results: Vec) -> Self; - - /// Build generic error response for any operation type - /// - /// # Parameters - /// - `error_code`: Predefined client request error code - #[allow(unused)] - fn client_error(error_code: ErrorCode) -> Self; - - /// Convert response to boolean write result - /// - /// # Returns - /// - `Ok(true)` on successful write - /// - `Err` with converted error code on failure - #[allow(unused)] - fn into_write_result(self) -> std::result::Result; - - /// Convert response to read results - /// - /// # Returns - /// Vector of optional key-value pairs wrapped in Result - #[allow(unused)] - fn into_read_results(self) -> std::result::Result>, ClientApiError>; - - /// Validate error code in response header - /// - /// # Internal Logic - /// Converts numeric error code to enum variant - #[allow(unused)] - fn validate_error(&self) -> std::result::Result<(), ClientApiError>; - - /// Check if this response indicates the leader's term is outdated - #[allow(unused)] - fn is_term_outdated(&self) -> bool; - - /// Check if this response indicates a quorum timeout or failure to receive majority responses - #[allow(unused)] - fn is_quorum_timeout_or_failure(&self) -> bool; - - /// Check if this response indicates a failure to receive majority responses - #[allow(unused)] - fn is_propose_failure(&self) -> bool; - - /// Check if this response indicates a a retry required - #[allow(unused)] - fn is_retry_required(&self) -> bool; -} - -pub trait WriteCommandExt { - /// Create write command for key-value pair - /// - /// # Parameters - /// - `key`: Byte array for storage key - /// - `value`: Byte array to be stored - fn insert( - key: impl Into, - value: impl Into, - ) -> Self; - - /// Create deletion command for specified key - /// - /// # Parameters - /// - `key`: Byte array of key to delete - fn delete(key: impl Into) -> Self; -} - -pub trait ErrorCodeExt { - /// Check if this error indicates the leader's term is outdated - fn is_term_outdated(&self) -> bool; - - /// Check if this error indicates a quorum timeout or failure to receive majority responses - fn is_quorum_timeout_or_failure(&self) -> bool; - - /// Check if this error indicates a failure to receive majority responses - fn is_propose_failure(&self) -> bool; - - /// Check if this error indicates a retry required - fn is_retry_required(&self) -> bool; -} - -impl ClientReadRequestExt for ClientReadRequest { - /// Checks if the consistency_policy field is present in the request - /// - /// Returns true if the client explicitly specified a consistency policy, - /// false if the field is absent (should use server default). - fn has_consistency_policy(&self) -> bool { - self.consistency_policy.is_some() - } - - /// Gets the consistency policy value safely - /// - /// Returns Some(policy) if present, None if field is absent. - /// Safer alternative that doesn't panic. - fn get_consistency_policy(&self) -> Option { - self.consistency_policy.and_then(|policy_i32| { - match policy_i32 { - x if x == ReadConsistencyPolicy::LeaseRead as i32 => { - Some(ReadConsistencyPolicy::LeaseRead) - } - x if x == ReadConsistencyPolicy::LinearizableRead as i32 => { - Some(ReadConsistencyPolicy::LinearizableRead) - } - _ => None, // Invalid value - } - }) - } -} - -impl WriteCommandExt for WriteCommand { - /// Create write command for key-value pair - /// - /// # Parameters - /// - `key`: Byte array for storage key - /// - `value`: Byte array to be stored - fn insert( - key: impl Into, - value: impl Into, - ) -> Self { - let cmd = write_command::Insert { - key: key.into(), - value: value.into(), - }; - Self { - operation: Some(write_command::Operation::Insert(cmd)), - } - } - - /// Create deletion command for specified key - /// - /// # Parameters - /// - `key`: Byte array of key to delete - fn delete(key: impl Into) -> Self { - let cmd = write_command::Delete { key: key.into() }; - Self { - operation: Some(write_command::Operation::Delete(cmd)), - } - } -} - -impl ClientResponseExt for ClientResponse { - /// Build success response for write operations - /// - /// # Returns - /// Response with NoError code and write confirmation - fn write_success() -> Self { - Self { - error: ErrorCode::Success as i32, - success_result: Some(SuccessResult::WriteAck(true)), - metadata: None, - } - } - - /// Check if the write operation was successful - /// - /// # Returns - /// - `true` if the response indicates a successful write operation - /// - `false` if the response indicates a failed write operation or is not a write response - fn is_write_success(&self) -> bool { - self.error == ErrorCode::Success as i32 - && matches!(self.success_result, Some(SuccessResult::WriteAck(true))) - } - - /// Build success response for read operations - /// - /// # Parameters - /// - `results`: Vector of retrieved key-value pairs - fn read_results(results: Vec) -> Self { - Self { - error: ErrorCode::Success as i32, - success_result: Some(SuccessResult::ReadData(ReadResults { results })), - metadata: None, - } - } - - /// Build generic error response for any operation type - /// - /// # Parameters - /// - `error_code`: Predefined client request error code - fn client_error(error_code: ErrorCode) -> Self { - Self { - error: error_code as i32, - success_result: None, - metadata: None, - } - } - - /// Convert response to boolean write result - /// - /// # Returns - /// - `Ok(true)` on successful write - /// - `Err` with converted error code on failure - fn into_write_result(self) -> std::result::Result { - self.validate_error()?; - Ok(match self.success_result { - Some(SuccessResult::WriteAck(success)) => success, - _ => false, - }) - } - - /// Convert response to read results - /// - /// # Returns - /// Vector of optional key-value pairs wrapped in Result - fn into_read_results(self) -> std::result::Result>, ClientApiError> { - self.validate_error()?; - match &self.success_result { - Some(SuccessResult::ReadData(data)) => data - .results - .clone() - .into_iter() - .map(|item| { - Ok(Some(ClientResult { - key: item.key, - value: item.value, - })) - }) - .collect(), - _ => { - error!("Invalid response type for read operation"); - unreachable!() - } - } - } - - /// Validate error code in response header - /// - /// # Internal Logic - /// Converts numeric error code to enum variant - fn validate_error(&self) -> std::result::Result<(), ClientApiError> { - match ErrorCode::try_from(self.error).unwrap_or(ErrorCode::Uncategorized) { - ErrorCode::Success => Ok(()), - e => Err(e.into()), - } - } - - /// Check if this response indicates the leader's term is outdated - fn is_term_outdated(&self) -> bool { - ErrorCode::try_from(self.error).map(|e| e.is_term_outdated()).unwrap_or(false) - } - - /// Check if this response indicates a quorum timeout or failure to receive majority responses - fn is_quorum_timeout_or_failure(&self) -> bool { - ErrorCode::try_from(self.error) - .map(|e| e.is_quorum_timeout_or_failure()) - .unwrap_or(false) - } - - /// Check if this response indicates a failure to receive majority responses - fn is_propose_failure(&self) -> bool { - ErrorCode::try_from(self.error).map(|e| e.is_propose_failure()).unwrap_or(false) - } - - /// Check if this response indicates a a retry required - fn is_retry_required(&self) -> bool { - ErrorCode::try_from(self.error).map(|e| e.is_retry_required()).unwrap_or(false) - } -} - -impl ErrorCodeExt for ErrorCode { - /// Check if this error indicates the leader's term is outdated - fn is_term_outdated(&self) -> bool { - matches!(self, ErrorCode::TermOutdated) - } - - /// Check if this error indicates a quorum timeout or failure to receive majority responses - fn is_quorum_timeout_or_failure(&self) -> bool { - matches!( - self, - ErrorCode::ConnectionTimeout | ErrorCode::ProposeFailed | ErrorCode::ClusterUnavailable - ) - } - - /// Check if this error indicates a failure to receive majority responses - fn is_propose_failure(&self) -> bool { - matches!(self, ErrorCode::ProposeFailed) - } - - /// Check if this error indicates a retry required - fn is_retry_required(&self) -> bool { - matches!(self, ErrorCode::RetryRequired) - } -} diff --git a/d-engine-client/src/lib.rs b/d-engine-client/src/lib.rs index fc2167a2..b42892c7 100644 --- a/d-engine-client/src/lib.rs +++ b/d-engine-client/src/lib.rs @@ -43,12 +43,12 @@ //! ``` mod builder; -mod client_extensions; mod cluster; mod config; mod error; mod kv; mod pool; +mod proto; mod scoped_timer; mod utils; @@ -58,6 +58,7 @@ pub use config::*; pub use error::*; pub use kv::*; pub use pool::*; +pub(crate) use proto::*; #[cfg(test)] mod cluster_test; diff --git a/d-engine-client/src/proto/client_ext.rs b/d-engine-client/src/proto/client_ext.rs new file mode 100644 index 00000000..20a675d4 --- /dev/null +++ b/d-engine-client/src/proto/client_ext.rs @@ -0,0 +1,95 @@ +pub trait ClientResponseExt { + /// Convert response to boolean write result + /// + /// # Returns + /// - `Ok(true)` on successful write + /// - `Err` with converted error code on failure + fn into_write_result(self) -> std::result::Result; + + /// Convert response to read results + /// + /// # Returns + /// Vector of optional key-value pairs wrapped in Result + fn into_read_results(self) -> std::result::Result>, ClientApiError> { + self.validate_error()?; + match &self.success_result { + Some(SuccessResult::ReadData(data)) => data + .results + .clone() + .into_iter() + .map(|item| { + Ok(Some(ClientResult { + key: item.key, + value: item.value, + })) + }) + .collect(), + _ => { + error!("Invalid response type for read operation"); + unreachable!() + } + } + } + + /// Validate error code in response header + /// + /// # Internal Logic + /// Converts numeric error code to enum variant + fn validate_error(&self) -> std::result::Result<(), ClientApiError> { + match ErrorCode::try_from(self.error).unwrap_or(ErrorCode::Uncategorized) { + ErrorCode::Success => Ok(()), + e => Err(e.into()), + } + } +} + +impl ClientResponseExt for ClientResponse { + /// Convert response to boolean write result + /// + /// # Returns + /// - `Ok(true)` on successful write + /// - `Err` with converted error code on failure + fn into_write_result(self) -> std::result::Result { + self.validate_error()?; + Ok(match self.success_result { + Some(SuccessResult::WriteAck(success)) => success, + _ => false, + }) + } + + /// Convert response to read results + /// + /// # Returns + /// Vector of optional key-value pairs wrapped in Result + fn into_read_results(self) -> std::result::Result>, ClientApiError> { + self.validate_error()?; + match &self.success_result { + Some(SuccessResult::ReadData(data)) => data + .results + .clone() + .into_iter() + .map(|item| { + Ok(Some(ClientResult { + key: item.key, + value: item.value, + })) + }) + .collect(), + _ => { + error!("Invalid response type for read operation"); + unreachable!() + } + } + } + + /// Validate error code in response header + /// + /// # Internal Logic + /// Converts numeric error code to enum variant + fn validate_error(&self) -> std::result::Result<(), ClientApiError> { + match ErrorCode::try_from(self.error).unwrap_or(ErrorCode::Uncategorized) { + ErrorCode::Success => Ok(()), + e => Err(e.into()), + } + } +} diff --git a/d-engine-client/src/proto/mod.rs b/d-engine-client/src/proto/mod.rs new file mode 100644 index 00000000..d8897868 --- /dev/null +++ b/d-engine-client/src/proto/mod.rs @@ -0,0 +1,120 @@ +impl ClientResponse { + /// Build success response for write operations + /// + /// # Returns + /// Response with NoError code and write confirmation + pub fn write_success() -> Self { + Self { + error: ErrorCode::Success as i32, + success_result: Some(SuccessResult::WriteAck(true)), + metadata: None, + } + } + + /// Check if the write operation was successful + /// + /// # Returns + /// - `true` if the response indicates a successful write operation + /// - `false` if the response indicates a failed write operation or is not a write response + pub fn is_write_success(&self) -> bool { + self.error == ErrorCode::Success as i32 + && matches!(self.success_result, Some(SuccessResult::WriteAck(true))) + } + + /// Build success response for read operations + /// + /// # Parameters + /// - `results`: Vector of retrieved key-value pairs + pub fn read_results(results: Vec) -> Self { + Self { + error: ErrorCode::Success as i32, + success_result: Some(SuccessResult::ReadData(ReadResults { results })), + metadata: None, + } + } + + /// Build generic error response for any operation type + /// + /// # Parameters + /// - `error_code`: Predefined client request error code + pub fn client_error(error_code: ErrorCode) -> Self { + Self { + error: error_code as i32, + success_result: None, + metadata: None, + } + } + + /// Convert response to boolean write result + /// + /// # Returns + /// - `Ok(true)` on successful write + /// - `Err` with converted error code on failure + pub fn into_write_result(self) -> std::result::Result { + self.validate_error()?; + Ok(match self.success_result { + Some(SuccessResult::WriteAck(success)) => success, + _ => false, + }) + } + + /// Convert response to read results + /// + /// # Returns + /// Vector of optional key-value pairs wrapped in Result + pub fn into_read_results( + self + ) -> std::result::Result>, ClientApiError> { + self.validate_error()?; + match &self.success_result { + Some(SuccessResult::ReadData(data)) => data + .results + .clone() + .into_iter() + .map(|item| { + Ok(Some(ClientResult { + key: item.key, + value: item.value, + })) + }) + .collect(), + _ => { + error!("Invalid response type for read operation"); + unreachable!() + } + } + } + + /// Validate error code in response header + /// + /// # Internal Logic + /// Converts numeric error code to enum variant + pub fn validate_error(&self) -> std::result::Result<(), ClientApiError> { + match ErrorCode::try_from(self.error).unwrap_or(ErrorCode::Uncategorized) { + ErrorCode::Success => Ok(()), + e => Err(e.into()), + } + } + + /// Check if this response indicates the leader's term is outdated + pub fn is_term_outdated(&self) -> bool { + ErrorCode::try_from(self.error).map(|e| e.is_term_outdated()).unwrap_or(false) + } + + /// Check if this response indicates a quorum timeout or failure to receive majority responses + pub fn is_quorum_timeout_or_failure(&self) -> bool { + ErrorCode::try_from(self.error) + .map(|e| e.is_quorum_timeout_or_failure()) + .unwrap_or(false) + } + + /// Check if this response indicates a failure to receive majority responses + pub fn is_propose_failure(&self) -> bool { + ErrorCode::try_from(self.error).map(|e| e.is_propose_failure()).unwrap_or(false) + } + + /// Check if this response indicates a a retry required + pub fn is_retry_required(&self) -> bool { + ErrorCode::try_from(self.error).map(|e| e.is_retry_required()).unwrap_or(false) + } +} diff --git a/d-engine-core/Cargo.toml b/d-engine-core/Cargo.toml index 9d60e03f..85b051e6 100644 --- a/d-engine-core/Cargo.toml +++ b/d-engine-core/Cargo.toml @@ -13,13 +13,20 @@ tracing = { workspace = true } futures = { workspace = true } serde = { workspace = true } prost = { workspace = true } - +bincode = "1.3" +thiserror = "1.0" nanoid = "0.4.0" # using Dashset dashmap = "5.5.3" +tempfile = "3.19.1" metrics = { version = "0.24", features = [] } config = { version = "0.14.0", default-features = false, features = ["toml"] } +tokio-stream = "0.1.16" +# Packing/unpacking TAR archives +tokio-tar = "0.3" [dev-dependencies] mockall = "0.12.1" tracing-test = "0.2" +serial_test = "3.2.0" +temp-env = "0.3.6" diff --git a/d-engine-core/src/commit_handler/default_commit_handler_test.rs b/d-engine-core/src/commit_handler/default_commit_handler_test.rs index c3495570..b9239f1b 100644 --- a/d-engine-core/src/commit_handler/default_commit_handler_test.rs +++ b/d-engine-core/src/commit_handler/default_commit_handler_test.rs @@ -12,7 +12,6 @@ use tokio::time::{self}; use super::CommitHandler; use super::CommitHandlerDependencies; use super::DefaultCommitHandler; -use crate::CommitHandlerConfig; use crate::Error; use crate::MockMembership; use crate::MockRaftLog; @@ -24,6 +23,7 @@ use crate::RaftNodeConfig; use crate::Result; use crate::test_utils::MockTypeConfig; use crate::test_utils::generate_insert_commands; +use crate::{CommitHandlerConfig, EntryPayloadExt}; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; use d_engine_proto::common::LogId; diff --git a/src/config/cluster.rs b/d-engine-core/src/config/cluster.rs similarity index 96% rename from src/config/cluster.rs rename to d-engine-core/src/config/cluster.rs index 50a9de21..093b70eb 100644 --- a/src/config/cluster.rs +++ b/d-engine-core/src/config/cluster.rs @@ -6,11 +6,11 @@ use serde::Deserialize; use serde::Serialize; use super::validate_directory; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::NodeStatus; use crate::Error; use crate::Result; -use crate::FOLLOWER; +use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::server::cluster::NodeMeta; /// Cluster node configuration parameters /// @@ -129,7 +129,7 @@ fn default_initial_cluster() -> Vec { vec![NodeMeta { id: 1, address: "127.0.0.1:8080".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }] } diff --git a/src/config/config_test.rs b/d-engine-core/src/config/config_test.rs similarity index 99% rename from src/config/config_test.rs rename to d-engine-core/src/config/config_test.rs index e110af9e..01ba914b 100644 --- a/src/config/config_test.rs +++ b/d-engine-core/src/config/config_test.rs @@ -212,8 +212,8 @@ fn type_mismatch_in_config_should_fail_gracefully() { /// Tests for node join status detection mod join_status_tests { use super::*; - use crate::proto::cluster::NodeMeta; - use crate::proto::common::NodeStatus; + use d_engine_proto::common::NodeStatus; + use d_engine_proto::server::cluster::NodeMeta; /// # Case 1: Node is in joining status #[test] diff --git a/src/config/mod.rs b/d-engine-core/src/config/mod.rs similarity index 99% rename from src/config/mod.rs rename to d-engine-core/src/config/mod.rs index b9a7108a..28a6e0f0 100644 --- a/src/config/mod.rs +++ b/d-engine-core/src/config/mod.rs @@ -34,9 +34,9 @@ use config::File; use serde::Deserialize; use serde::Serialize; -use crate::proto::common::NodeStatus; use crate::Error; use crate::Result; +use d_engine_proto::common::NodeStatus; /// Main configuration container for Raft consensus engine components /// diff --git a/src/config/monitoring.rs b/d-engine-core/src/config/monitoring.rs similarity index 100% rename from src/config/monitoring.rs rename to d-engine-core/src/config/monitoring.rs diff --git a/src/config/network.rs b/d-engine-core/src/config/network.rs similarity index 100% rename from src/config/network.rs rename to d-engine-core/src/config/network.rs diff --git a/src/config/network_test.rs b/d-engine-core/src/config/network_test.rs similarity index 100% rename from src/config/network_test.rs rename to d-engine-core/src/config/network_test.rs diff --git a/src/config/raft.rs b/d-engine-core/src/config/raft.rs similarity index 96% rename from src/config/raft.rs rename to d-engine-core/src/config/raft.rs index 4996ff5d..efe003fa 100644 --- a/src/config/raft.rs +++ b/d-engine-core/src/config/raft.rs @@ -128,11 +128,11 @@ impl RaftConfig { // Warn if lease duration is too long compared to election timeout if self.read_consistency.lease_duration_ms > self.election.election_timeout_min / 2 { warn!( - "read_consistency.lease_duration_ms ({}) is greater than half of election_timeout_min ({}ms). \ + "read_consistency.lease_duration_ms ({}) is greater than half of election_timeout_min ({}ms). \ This may cause lease expiration during normal operation.", - self.read_consistency.lease_duration_ms, - self.election.election_timeout_min / 2 - ); + self.read_consistency.lease_duration_ms, + self.election.election_timeout_min / 2 + ); } Ok(()) @@ -696,7 +696,7 @@ fn default_stale_check_interval() -> Duration { /// /// **Note:** Both strategies now fully load all log entries from disk into memory at startup. /// The in-memory `SkipMap` serves as the primary data structure for reads in all modes. -#[doc = include_str!("../docs/architecture/raft-log-persistence-architecture.md")] +#[doc = include_str!("../../../d-engine-docs/src/docs/architecture/raft-log-persistence-architecture.md")] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum PersistenceStrategy { /// Disk-first persistence strategy. @@ -926,29 +926,31 @@ impl ReadConsistencyConfig { } } -impl From for ReadConsistencyPolicy { - fn from(proto_policy: crate::proto::client::ReadConsistencyPolicy) -> Self { +impl From for ReadConsistencyPolicy { + fn from(proto_policy: d_engine_proto::client::ReadConsistencyPolicy) -> Self { match proto_policy { - crate::proto::client::ReadConsistencyPolicy::LeaseRead => Self::LeaseRead, - crate::proto::client::ReadConsistencyPolicy::LinearizableRead => Self::LinearizableRead, - crate::proto::client::ReadConsistencyPolicy::EventualConsistency => { + d_engine_proto::client::ReadConsistencyPolicy::LeaseRead => Self::LeaseRead, + d_engine_proto::client::ReadConsistencyPolicy::LinearizableRead => { + Self::LinearizableRead + } + d_engine_proto::client::ReadConsistencyPolicy::EventualConsistency => { Self::EventualConsistency } } } } -impl From for crate::proto::client::ReadConsistencyPolicy { +impl From for d_engine_proto::client::ReadConsistencyPolicy { fn from(config_policy: ReadConsistencyPolicy) -> Self { match config_policy { ReadConsistencyPolicy::LeaseRead => { - crate::proto::client::ReadConsistencyPolicy::LeaseRead + d_engine_proto::client::ReadConsistencyPolicy::LeaseRead } ReadConsistencyPolicy::LinearizableRead => { - crate::proto::client::ReadConsistencyPolicy::LinearizableRead + d_engine_proto::client::ReadConsistencyPolicy::LinearizableRead } ReadConsistencyPolicy::EventualConsistency => { - crate::proto::client::ReadConsistencyPolicy::EventualConsistency + d_engine_proto::client::ReadConsistencyPolicy::EventualConsistency } } } diff --git a/src/config/raft_test.rs b/d-engine-core/src/config/raft_test.rs similarity index 100% rename from src/config/raft_test.rs rename to d-engine-core/src/config/raft_test.rs diff --git a/src/config/retry.rs b/d-engine-core/src/config/retry.rs similarity index 100% rename from src/config/retry.rs rename to d-engine-core/src/config/retry.rs diff --git a/src/config/security.rs b/d-engine-core/src/config/security.rs similarity index 100% rename from src/config/security.rs rename to d-engine-core/src/config/security.rs diff --git a/src/config/tls.rs b/d-engine-core/src/config/tls.rs similarity index 100% rename from src/config/tls.rs rename to d-engine-core/src/config/tls.rs diff --git a/src/config/tls_test.rs b/d-engine-core/src/config/tls_test.rs similarity index 100% rename from src/config/tls_test.rs rename to d-engine-core/src/config/tls_test.rs diff --git a/d-engine-core/src/lib.rs b/d-engine-core/src/lib.rs index 60149a5e..5c08ed55 100644 --- a/d-engine-core/src/lib.rs +++ b/d-engine-core/src/lib.rs @@ -1,31 +1,35 @@ -mod cluster_proto_extension; mod commit_handler; +pub mod config; mod election; mod errors; mod event; +mod maybe_clone_oneshot; mod membership; +mod network; mod purge; mod raft; mod raft_context; mod raft_role; mod replication; mod state_machine_handler; +mod storage; mod timer; mod type_config; mod utils; -pub mod storage; +pub use config::*; pub use membership::*; +pub use network::*; +pub use storage::*; -pub(crate) use cluster_proto_extension::*; pub(crate) use commit_handler::*; pub(crate) use election::*; pub(crate) use errors::*; pub(crate) use event::*; +pub(crate) use maybe_clone_oneshot::*; pub(crate) use purge::*; pub(crate) use raft::*; pub(crate) use raft_context::*; -pub use storage::*; #[doc(hidden)] pub use raft_role::*; @@ -97,24 +101,3 @@ pub enum QuorumStatus { LostQuorum, // Unable to obtain majority NetworkError, // Network problem (can be retried) } - -impl EntryPayload { - pub fn command(command: Bytes) -> Self { - Self { - payload: Some(Payload::Command(command)), - } - } - pub fn noop() -> Self { - Self { - payload: Some(Payload::Noop(Noop {})), - } - } - - pub fn config(change: Change) -> Self { - Self { - payload: Some(Payload::Config(MembershipChange { - change: Some(change), - })), - } - } -} diff --git a/src/node/type_config/maybe_clone_oneshot.rs b/d-engine-core/src/maybe_clone_oneshot.rs similarity index 100% rename from src/node/type_config/maybe_clone_oneshot.rs rename to d-engine-core/src/maybe_clone_oneshot.rs diff --git a/d-engine-core/src/network/mod.rs b/d-engine-core/src/network/mod.rs new file mode 100644 index 00000000..8e6c2e89 --- /dev/null +++ b/d-engine-core/src/network/mod.rs @@ -0,0 +1,366 @@ +//! This module is the network abstraction layer with timeout-aware gRPC +//! implementation +//! +//! This module provides network communication facilities with configurable +//! timeout policies for distributed system operations. All network operations +//! are governed by timeout parameters defined in [`RaftConfig`] to ensure +//! system responsiveness. + +// #[cfg(test)] +// mod network_test; + +#[cfg(test)] +use mockall::automock; +use tonic::async_trait; + +use crate::BackoffPolicy; +use crate::NetworkError; +use crate::Result; +use crate::RetryPolicies; +use crate::TypeConfig; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::storage::PurgeLogRequest; +use d_engine_proto::server::storage::PurgeLogResponse; +use d_engine_proto::server::storage::SnapshotChunk; + +// Define a structured return value +#[derive(Debug, Clone)] +pub(crate) struct AppendResults { + /// Whether a majority quorum is achieved (can be directly determined via + /// peer_updates) + pub commit_quorum_achieved: bool, + /// Updates to each peer's match_index and next_index + pub peer_updates: HashMap, + /// Learner log catch-up progress + pub learner_progress: HashMap>, +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct PeerUpdate { + pub match_index: Option, + pub next_index: u64, + /// if peer response success + pub success: bool, +} + +impl PeerUpdate { + #[allow(unused)] + pub fn success( + match_index: u64, + next_index: u64, + ) -> Self { + PeerUpdate { + match_index: Some(match_index), + next_index, + success: true, + } + } + + #[allow(unused)] + pub fn failed() -> Self { + Self { + match_index: None, + next_index: 1, + success: false, + } + } +} +#[derive(Debug)] +pub struct AppendResult { + pub peer_ids: HashSet, + pub responses: Vec>, +} +#[derive(Debug)] +pub struct VoteResult { + pub peer_ids: HashSet, + pub responses: Vec>, +} +#[allow(dead_code)] +#[derive(Debug)] +pub struct ClusterUpdateResult { + pub peer_ids: HashSet, + pub responses: Vec>, +} + +#[cfg_attr(test, automock)] +#[async_trait] +pub trait Transport: Send + Sync + 'static +where + T: TypeConfig, +{ + /// Propagates cluster configuration changes to voting members using Raft's joint consensus. + /// + /// # Protocol + /// - Implements membership change protocol from Raft §6 + /// - Leader-exclusive operation + /// - Automatically filters self-references and duplicates + /// + /// # Parameters + /// - `req`: Configuration change details with transition state + /// - `retry`: Network retry policy with exponential backoff + /// - `membership`: Cluster membership for channel resolution + /// + /// # Errors + /// - `NetworkError::EmptyPeerList` if no peers provided + /// - `NetworkError::TaskFailed` for background execution failures + /// - `ConsensusError::NotLeader` if executed by non-leader + /// + /// # Implementation + /// - Uses compressed gRPC streams for efficiency + /// - Maintains response order matching input peers + /// - Concurrent request processing with ordered aggregation + #[allow(dead_code)] + async fn send_cluster_update( + &self, + req: ClusterConfChangeRequest, + retry: &RetryPolicies, + membership: std::sync::Arc>, + ) -> Result; + + /// Replicates log entries to followers and learners. + /// + /// # Protocol + /// - Implements log replication from Raft §5.3 + /// - Leader-exclusive operation + /// - Handles log consistency checks automatically + /// + /// # Parameters + /// - `requests`: Vector of (peer_id, AppendEntriesRequest) + /// - `retry`: Network retry configuration + /// - `membership`: Cluster membership for channel resolution + /// - `response_compress_enabled`: Enable compression for replication responses + /// + /// # Returns + /// - On success: `Ok(AppendResult)` containing aggregated responses + /// - On failure: `Err(NetworkError)` for unrecoverable errors + /// + /// ## **Error Conditions**: Top-level `Err` is returned ONLY when: + /// - Input `requests_with_peer_address` is empty (`NetworkError::EmptyPeerList`) + /// - Critical failures prevent spawning async tasks (not shown in current impl) + /// + /// # Errors + /// - `NetworkError::EmptyPeerList` for empty input + /// - `NetworkError::TaskFailed` for partial execution failures + /// + /// # Guarantees + /// - At-least-once delivery semantics + /// - Automatic deduplication of peer entries + /// - Non-blocking error handling + async fn send_append_requests( + &self, + requests: Vec<(u32, AppendEntriesRequest)>, + retry: &RetryPolicies, + membership: std::sync::Arc>, + response_compress_enabled: bool, + ) -> Result; + + /// Initiates leader election by requesting votes from cluster peers. + /// + /// # Protocol + /// - Implements leader election from Raft §5.2 + /// - Candidate-exclusive operation + /// - Validates log completeness requirements + /// + /// # Parameters + /// - `req`: Election metadata with candidate's term and log state + /// - `retry`: Election-specific retry strategy + /// - `membership`: Cluster membership for channel resolution + /// + /// # Errors + /// - `NetworkError::EmptyPeerList` for empty peer list + /// - `NetworkError::TaskFailed` for RPC execution failures + /// + /// # Safety + /// - Automatic term validation in responses + /// - Strict candidate state enforcement + /// - Non-blocking partial failure handling + async fn send_vote_requests( + &self, + req: VoteRequest, + retry: &RetryPolicies, + membership: std::sync::Arc>, + ) -> Result; + + /// Orchestrates log compaction across cluster peers after snapshot creation. + /// + /// # Protocol + /// - Implements log truncation from Raft §7 + /// - Leader-exclusive operation + /// - Requires valid snapshot checksum + /// + /// # Parameters + /// - `req`: Snapshot metadata with truncation index + /// - `retry`: Purge-specific retry configuration + /// - `membership`: Cluster membership for channel resolution + /// + /// # Errors + /// - `NetworkError::EmptyPeerList` for empty peer list + /// - `NetworkError::TaskFailed` for background execution errors + /// + /// # Guarantees + /// - At-least-once delivery + /// - Automatic progress tracking + /// - Crash-safe persistence requirements + async fn send_purge_requests( + &self, + req: PurgeLogRequest, + retry: &RetryPolicies, + membership: std::sync::Arc>, + ) -> Result>>; + + /// Initiates cluster join process for a learner node + /// + /// # Protocol + /// - Implements cluster join protocol from Raft §6 + /// - Learner-exclusive operation + /// - Requires leader connection + /// + /// # Parameters + /// - `leader_channel`: Pre-established gRPC channel to cluster leader + /// - `request`: Join request with node metadata + /// - `retry`: Join-specific retry configuration + /// - `membership`: Cluster membership for channel resolution + /// + /// # Errors + /// - NetworkError::JoinFailed: On unrecoverable join failure + /// - NetworkError::NotLeader: If contacted node isn't leader + /// + /// # Guarantees + /// - At-least-once delivery + /// - Automatic leader discovery + /// - Idempotent operation + async fn join_cluster( + &self, + leader_id: u32, + request: d_engine_proto::server::cluster::JoinRequest, + retry: BackoffPolicy, + membership: std::sync::Arc>, + ) -> Result; + + /// Discovers current cluster leader + /// + /// # Protocol + /// - Broadcast-based leader discovery + /// - Handles redirection to current leader + /// + /// # Parameters + /// - `bootstrap_endpoints`: Initial cluster endpoints + /// - `request`: Discovery request with node metadata + /// + /// # Errors + /// - `NetworkError::DiscoveryTimeout`: When no response received + async fn discover_leader( + &self, + request: d_engine_proto::server::cluster::LeaderDiscoveryRequest, + rpc_enable_compression: bool, + membership: std::sync::Arc>, + ) -> Result>; + + /// Requests and streams a snapshot from the current leader. + /// + /// # Parameters + /// - `leader_id`: Current leader node ID + /// - `retry`: Retry configuration (currently unused in implementation) + /// - `membership`: Cluster membership for channel resolution + /// + /// # Returns + /// Streaming of snapshot chunks from the leader + /// + /// # Errors + /// Returns `NetworkError` if: + /// - Connection to leader fails + /// - gRPC call fails + async fn request_snapshot_from_leader( + &self, + leader_id: u32, + ack_tx: tokio::sync::mpsc::Receiver, + retry: &crate::InstallSnapshotBackoffPolicy, + membership: std::sync::Arc>, + ) -> Result>>; +} + +// Module level utils +// ----------------------------------------------------------------------------- +use std::collections::HashMap; +use std::collections::HashSet; +use std::time::Duration; + +use tokio::time::sleep; +use tokio::time::timeout; +use tonic::Code; +use tracing::debug; +use tracing::warn; + +use crate::Error; + +/// As soon as task has return we should return from this function +pub(crate) async fn grpc_task_with_timeout_and_exponential_backoff( + task_name: &'static str, + mut task: F, + policy: BackoffPolicy, +) -> std::result::Result, Error> +where + F: FnMut() -> T, + T: std::future::Future, tonic::Status>> + + Send + + 'static, +{ + // let max_retries = 5; + let mut retries = 0; + let mut current_delay = Duration::from_millis(policy.base_delay_ms); + let timeout_duration = Duration::from_millis(policy.timeout_ms); + let max_delay = Duration::from_millis(policy.max_delay_ms); + let max_retries = policy.max_retries; + + let mut last_error = + NetworkError::TaskBackoffFailed("Task failed after max retries".to_string()); + while retries < max_retries { + debug!("[{task_name}] Attempt {} of {}", retries + 1, max_retries); + match timeout(timeout_duration, task()).await { + Ok(Ok(r)) => { + return Ok(r); // Exit on success + } + Ok(Err(status)) => { + last_error = match status.code() { + Code::Unavailable => { + warn!("[{task_name}] Service unavailable: {}", status.message()); + NetworkError::ServiceUnavailable(format!( + "Service unavailable: {}", + status.message() + )) + } + _ => { + warn!("[{task_name}] RPC error: {}", status); + NetworkError::TonicStatusError(Box::new(status)) + } + }; + } + Err(_e) => { + warn!("[{task_name}] Task timed out after {:?}", timeout_duration); + last_error = NetworkError::RetryTimeoutError(timeout_duration); + } + }; + + if retries < max_retries - 1 { + debug!("[{task_name}] Retrying in {:?}...", current_delay); + sleep(current_delay).await; + + // Exponential backoff (double the delay each time) + current_delay = (current_delay * 2).min(max_delay); + } else { + warn!("[{task_name}] Task failed after {} retries", retries); + //bug: no need to return if the it is not a business logic error + // return Err(Error::RetryTaskFailed("Task failed after max + // retries".to_string())); // Return the last error after max + // retries + } + retries += 1; + } + warn!("[{task_name}] Task failed after {} retries", max_retries); + Err(last_error.into()) // Fallback error message if no task returns Ok +} diff --git a/d-engine-core/src/purge/mod.rs b/d-engine-core/src/purge/mod.rs index 72aa0e79..521abf67 100644 --- a/d-engine-core/src/purge/mod.rs +++ b/d-engine-core/src/purge/mod.rs @@ -1,4 +1,4 @@ -#![doc = include_str!("../../docs/architecture/raft-log-persistence-architecture.md")] +#![doc = include_str!("../../../d-engine-docs/src/docs/architecture/raft-log-persistence-architecture.md")] //! Log compaction and purge management. //! //! Defines the interface for customizing log purge behavior in Raft leaders and followers. diff --git a/d-engine-core/src/raft_role/candidate_state.rs b/d-engine-core/src/raft_role/candidate_state.rs index ba1c6341..61b5eacc 100644 --- a/d-engine-core/src/raft_role/candidate_state.rs +++ b/d-engine-core/src/raft_role/candidate_state.rs @@ -606,7 +606,7 @@ impl CandidateState { current_term: self.current_term(), voted_for: None, commit_index: self.commit_index(), - role: Candidate, + role: Candidate.into(), } } diff --git a/d-engine-core/src/raft_role/candidate_state_test.rs b/d-engine-core/src/raft_role/candidate_state_test.rs index d05bfcb2..fb57283f 100644 --- a/d-engine-core/src/raft_role/candidate_state_test.rs +++ b/d-engine-core/src/raft_role/candidate_state_test.rs @@ -686,7 +686,7 @@ async fn test_handle_raft_event_case10() { // Step 2: Prepare the event let request = JoinRequest { node_id: 2, - node_role: Learner, + node_role: Learner.into(), address: "127.0.0.1:9090".to_string(), }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); diff --git a/d-engine-core/src/raft_role/follower_state.rs b/d-engine-core/src/raft_role/follower_state.rs index c4f6bbce..532de443 100644 --- a/d-engine-core/src/raft_role/follower_state.rs +++ b/d-engine-core/src/raft_role/follower_state.rs @@ -634,7 +634,7 @@ impl FollowerState { #[tracing::instrument] pub fn state_snapshot(&self) -> StateSnapshot { StateSnapshot { - role: Follower, + role: Follower.into(), current_term: self.current_term(), voted_for: None, commit_index: self.commit_index(), diff --git a/d-engine-core/src/raft_role/leader_state_test.rs b/d-engine-core/src/raft_role/leader_state_test.rs index 5c980901..2e69ed58 100644 --- a/d-engine-core/src/raft_role/leader_state_test.rs +++ b/d-engine-core/src/raft_role/leader_state_test.rs @@ -3108,7 +3108,7 @@ mod trigger_background_snapshot_test { fn make_metadata() -> SnapshotMetadata { SnapshotMetadata { - last_included: Some(crate::proto::common::LogId { index: 1, term: 1 }), + last_included: Some(d_engine_proto::common::LogId { index: 1, term: 1 }), ..Default::default() } } diff --git a/d-engine-core/src/raft_test.rs b/d-engine-core/src/raft_test.rs index cf462cff..7f18ce1b 100644 --- a/d-engine-core/src/raft_test.rs +++ b/d-engine-core/src/raft_test.rs @@ -402,7 +402,7 @@ async fn test_election_timeout_case4() { NodeMeta { id: peer2_id, address: "http://127.0.0.1:55002".to_string(), - role: Candidate, + role: Candidate.into(), status: NodeStatus::Active.into(), }, ] diff --git a/d-engine-core/src/replication/mod.rs b/d-engine-core/src/replication/mod.rs index 2159d655..64bc781a 100644 --- a/d-engine-core/src/replication/mod.rs +++ b/d-engine-core/src/replication/mod.rs @@ -221,73 +221,3 @@ where raft_log: &Arc>, ) -> AppendEntriesResponse; } - -impl AppendEntriesResponse { - /// Generate a successful response (full success) - pub fn success( - node_id: u32, - term: u64, - last_match: Option, - ) -> Self { - Self { - node_id, - term, - result: Some(append_entries_response::Result::Success(SuccessResult { - last_match, - })), - } - } - - /// Generate conflict response (with conflict details) - pub fn conflict( - node_id: u32, - term: u64, - conflict_term: Option, - conflict_index: Option, - ) -> Self { - Self { - node_id, - term, - result: Some(append_entries_response::Result::Conflict(ConflictResult { - conflict_term, - conflict_index, - })), - } - } - - /// Generate a conflict response (Higher term found) - pub fn higher_term( - node_id: u32, - term: u64, - ) -> Self { - Self { - node_id, - term, - result: Some(append_entries_response::Result::HigherTerm(term)), - } - } - - /// Check if it is a success response - pub fn is_success(&self) -> bool { - matches!( - &self.result, - Some(append_entries_response::Result::Success(_)) - ) - } - - /// Check if it is a conflict response - pub fn is_conflict(&self) -> bool { - matches!( - &self.result, - Some(append_entries_response::Result::Conflict(_conflict)) - ) - } - - /// Check if it is a response of a higher Term - pub fn is_higher_term(&self) -> bool { - matches!( - &self.result, - Some(append_entries_response::Result::HigherTerm(_)) - ) - } -} diff --git a/d-engine-core/src/replication/replication_handler.rs b/d-engine-core/src/replication/replication_handler.rs index 931a4d3d..5c38b029 100644 --- a/d-engine-core/src/replication/replication_handler.rs +++ b/d-engine-core/src/replication/replication_handler.rs @@ -25,11 +25,11 @@ use crate::RaftContext; use crate::RaftLog; use crate::ReplicationError; use crate::Result; +use crate::ScopedTimer; use crate::StateSnapshot; use crate::Transport; use crate::TypeConfig; use crate::alias::ROF; -use crate::scoped_timer::ScopedTimer; use crate::utils::cluster::is_majority; use d_engine_proto::client::WriteCommand; use d_engine_proto::common::Entry; diff --git a/d-engine-core/src/state_machine_handler/mod.rs b/d-engine-core/src/state_machine_handler/mod.rs index f05fa5b9..1e7cd986 100644 --- a/d-engine-core/src/state_machine_handler/mod.rs +++ b/d-engine-core/src/state_machine_handler/mod.rs @@ -81,7 +81,7 @@ where /// Applies a batch of committed log entries to the state machine async fn apply_chunk( &self, - chunk: Vec, + chunk: Vec, ) -> Result<()>; /// Reads values from the state machine for given keys @@ -97,7 +97,7 @@ where &self, current_term: u64, stream: Box>, - ack_tx: tokio::sync::mpsc::Sender, + ack_tx: tokio::sync::mpsc::Sender, config: &crate::SnapshotConfig, ) -> Result<()>; diff --git a/d-engine-core/src/state_machine_handler/snapshot_policy/composite_test.rs b/d-engine-core/src/state_machine_handler/snapshot_policy/composite_test.rs index 958d6d61..1f78f7b0 100644 --- a/d-engine-core/src/state_machine_handler/snapshot_policy/composite_test.rs +++ b/d-engine-core/src/state_machine_handler/snapshot_policy/composite_test.rs @@ -3,14 +3,14 @@ use std::time::Duration; use tracing_test::traced_test; use super::*; -use crate::Leader; use crate::core::state_machine_handler::snapshot_policy::SnapshotContext; use crate::core::state_machine_handler::snapshot_policy::SnapshotPolicy; use d_engine_proto::common::LogId; use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeRole::Leader; -const LEADER_ROLE: i32 = Leader; -const FOLLOWER_ROLE: i32 = Follower; +const LEADER_ROLE: i32 = Leader.into; +const FOLLOWER_ROLE: i32 = Follower.into(); /// Creates a leader context for testing fn leader_ctx(last_applied_index: u64) -> SnapshotContext { diff --git a/d-engine-core/src/storage/buffered/buffered_raft_log.rs b/d-engine-core/src/storage/buffered/buffered_raft_log.rs index e81622d7..5de0ac1b 100644 --- a/d-engine-core/src/storage/buffered/buffered_raft_log.rs +++ b/d-engine-core/src/storage/buffered/buffered_raft_log.rs @@ -16,32 +16,28 @@ use std::collections::HashMap; use std::ops::RangeInclusive; +use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; -use std::sync::Arc; use std::time::Duration; -use crossbeam::channel::bounded; use crossbeam::channel::Sender; +use crossbeam::channel::bounded; use crossbeam_skiplist::SkipMap; use dashmap::DashMap; +use tokio::sync::Mutex; use tokio::sync::mpsc; use tokio::sync::oneshot; -use tokio::sync::Mutex; use tokio::task::JoinHandle; -use tokio::time::interval; use tokio::time::Instant; +use tokio::time::interval; use tonic::async_trait; use tracing::debug; use tracing::error; use tracing::trace; use tracing::warn; -use crate::alias::SOF; -use crate::proto::common::Entry; -use crate::proto::common::LogId; -use crate::scoped_timer::ScopedTimer; use crate::Error; use crate::FlushPolicy; use crate::HardState; @@ -54,6 +50,10 @@ use crate::RaftLog; use crate::Result; use crate::StorageEngine; use crate::TypeConfig; +use crate::alias::SOF; +use crate::scoped_timer::ScopedTimer; +use d_engine_proto::common::Entry; +use d_engine_proto::common::LogId; pub struct FlushWorkerPool where @@ -1066,11 +1066,11 @@ where metrics::histogram!("flush_worker.success_duration") .record(duration.as_micros() as f64); debug!( - "Worker {} successfully processed flush of {} entries in {:?}", - worker_id, - indexes.len(), - duration - ); + "Worker {} successfully processed flush of {} entries in {:?}", + worker_id, + indexes.len(), + duration + ); } Err(e) => { metrics::counter!("flush_worker.errors").increment(1); diff --git a/d-engine-core/src/storage/buffered/buffered_raft_log_test.rs b/d-engine-core/src/storage/buffered/buffered_raft_log_test.rs index 20a6d408..7aa8c6d1 100644 --- a/d-engine-core/src/storage/buffered/buffered_raft_log_test.rs +++ b/d-engine-core/src/storage/buffered/buffered_raft_log_test.rs @@ -1,28 +1,20 @@ use std::collections::HashMap; use std::path::PathBuf; +use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; -use std::sync::Arc; use std::time::Duration; use std::vec; use bytes::Bytes; use futures::future::join_all; use tempfile::tempdir; -use tokio::time::sleep; use tokio::time::Instant; +use tokio::time::sleep; use tracing::debug; use tracing_test::traced_test; use super::*; -use crate::alias::ROF; -use crate::proto::common::Entry; -use crate::proto::common::EntryPayload; -use crate::proto::common::LogId; -use crate::test_utils::generate_insert_commands; -use crate::test_utils::MockStorageEngine; -use crate::test_utils::MockTypeConfig; -use crate::test_utils::{self}; use crate::BufferedRaftLog; use crate::FileStorageEngine; use crate::FlushPolicy; @@ -35,6 +27,14 @@ use crate::PersistenceStrategy; use crate::RaftLog; use crate::RaftTypeConfig; use crate::StorageEngine; +use crate::alias::ROF; +use crate::test_utils::MockStorageEngine; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::generate_insert_commands; +use crate::test_utils::{self}; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::LogId; // Test utilities struct TestContext { @@ -1604,8 +1604,8 @@ async fn test_term_index_performance_large_dataset() { mod id_allocation_tests { use std::collections::HashSet; use std::ops::RangeInclusive; - use std::sync::atomic::Ordering; use std::sync::Arc; + use std::sync::atomic::Ordering; use super::*; use crate::FileStorageEngine; @@ -2539,9 +2539,9 @@ mod performance_tests { use tokio::time::Duration; use super::*; - use crate::test_utils::MockStorageEngine; use crate::MockLogStore; use crate::MockMetaStore; + use crate::test_utils::MockStorageEngine; // Test helper: Creates storage with controllable delay fn create_delayed_storage(delay_ms: u64) -> Arc { @@ -2816,14 +2816,14 @@ mod performance_tests { } mod batch_processor_tests { + use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; - use std::sync::Arc; use std::time::Duration; + use tokio::sync::Notify; use tokio::sync::mpsc; use tokio::sync::oneshot; - use tokio::sync::Notify; use super::*; @@ -2953,12 +2953,12 @@ mod batch_processor_tests { mod save_load_hard_state_tests { use super::*; - use crate::proto::election::VotedFor; use crate::FileStorageEngine; use crate::HardState; use crate::LogStore; use crate::MetaStore; use crate::StorageEngine; + use d_engine_proto::election::VotedFor; /// Test that hard state operations use the meta tree and not the log tree #[tokio::test] diff --git a/d-engine-core/src/storage/raft_log.rs b/d-engine-core/src/storage/raft_log.rs index 26e12387..8ac503ce 100644 --- a/d-engine-core/src/storage/raft_log.rs +++ b/d-engine-core/src/storage/raft_log.rs @@ -6,11 +6,11 @@ use std::ops::RangeInclusive; use mockall::automock; use tonic::async_trait; -use crate::proto::common::entry_payload::Payload; -use crate::proto::common::Entry; -use crate::proto::common::EntryPayload; -use crate::proto::common::LogId; use crate::Result; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::LogId; +use d_engine_proto::common::entry_payload::Payload; // /// Configurable persistence strategy for Raft logs // #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] diff --git a/d-engine-core/src/storage/snapshot_path_manager.rs b/d-engine-core/src/storage/snapshot_path_manager.rs index c525a4e6..624ef1f6 100644 --- a/d-engine-core/src/storage/snapshot_path_manager.rs +++ b/d-engine-core/src/storage/snapshot_path_manager.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use crate::proto::common::LogId; +use d_engine_proto::common::LogId; /// Centralized manager for all snapshot-related path generation and naming conventions. /// diff --git a/d-engine-core/src/storage/state_machine.rs b/d-engine-core/src/storage/state_machine.rs index 020a051a..508f6475 100644 --- a/d-engine-core/src/storage/state_machine.rs +++ b/d-engine-core/src/storage/state_machine.rs @@ -5,11 +5,11 @@ use bytes::Bytes; use mockall::automock; use tonic::async_trait; -use crate::proto::common::Entry; -use crate::proto::common::LogId; -use crate::proto::storage::SnapshotMetadata; use crate::ConvertError; use crate::Error; +use d_engine_proto::common::Entry; +use d_engine_proto::common::LogId; +use d_engine_proto::server::storage::SnapshotMetadata; #[cfg_attr(test, automock)] #[async_trait] diff --git a/d-engine-core/src/storage/state_machine_test.rs b/d-engine-core/src/storage/state_machine_test.rs index 9638f259..8ced4d23 100644 --- a/d-engine-core/src/storage/state_machine_test.rs +++ b/d-engine-core/src/storage/state_machine_test.rs @@ -5,17 +5,17 @@ use prost::Message; use tempfile::TempDir; use tonic::async_trait; -use crate::proto::client::write_command::Delete; -use crate::proto::client::write_command::Insert; -use crate::proto::client::write_command::Operation; -use crate::proto::client::WriteCommand; -use crate::proto::common::entry_payload::Payload; -use crate::proto::common::Entry; -use crate::proto::common::EntryPayload; -use crate::proto::common::LogId; -use crate::proto::storage::SnapshotMetadata; -use crate::storage::StateMachine; use crate::Error; +use crate::storage::StateMachine; +use d_engine_proto::client::WriteCommand; +use d_engine_proto::client::write_command::Delete; +use d_engine_proto::client::write_command::Insert; +use d_engine_proto::client::write_command::Operation; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::LogId; +use d_engine_proto::common::entry_payload::Payload; +use d_engine_proto::server::storage::SnapshotMetadata; /// Test suite for StateMachine implementations /// diff --git a/d-engine-core/src/storage/storage_engine.rs b/d-engine-core/src/storage/storage_engine.rs index e1ca6280..755b7ff2 100644 --- a/d-engine-core/src/storage/storage_engine.rs +++ b/d-engine-core/src/storage/storage_engine.rs @@ -7,10 +7,10 @@ use std::sync::Arc; use mockall::automock; use tonic::async_trait; -use crate::proto::common::Entry; -use crate::proto::common::LogId; use crate::Error; use crate::HardState; +use d_engine_proto::common::Entry; +use d_engine_proto::common::LogId; /// High-performance storage abstraction for Raft consensus /// diff --git a/d-engine-core/src/storage/storage_engine_test.rs b/d-engine-core/src/storage/storage_engine_test.rs index dd33a930..fabb0f2e 100644 --- a/d-engine-core/src/storage/storage_engine_test.rs +++ b/d-engine-core/src/storage/storage_engine_test.rs @@ -5,15 +5,15 @@ use bytes::Bytes; use prost::Message; use tonic::async_trait; -use crate::proto::client::write_command::Insert; -use crate::proto::common::Entry; -use crate::proto::common::LogId; -use crate::proto::election::VotedFor; -use crate::storage::StorageEngine; use crate::Error; use crate::HardState; use crate::LogStore; use crate::MetaStore; +use crate::storage::StorageEngine; +use d_engine_proto::client::write_command::Insert; +use d_engine_proto::common::Entry; +use d_engine_proto::common::LogId; +use d_engine_proto::election::VotedFor; /// Test suite for StorageEngine implementations /// @@ -280,21 +280,21 @@ fn create_test_entries(range: RangeInclusive) -> Vec { } /// Helper function to create test command payload -fn create_test_command_payload(index: u64) -> crate::proto::common::EntryPayload { +fn create_test_command_payload(index: u64) -> d_engine_proto::common::EntryPayload { // Create a simple insert command let key = Bytes::from(format!("key_{index}")); let value = Bytes::from(format!("value_{index}")); let insert = Insert { key, value }; - let operation = crate::proto::client::write_command::Operation::Insert(insert); - let write_cmd = crate::proto::client::WriteCommand { + let operation = d_engine_proto::client::write_command::Operation::Insert(insert); + let write_cmd = d_engine_proto::client::WriteCommand { operation: Some(operation), }; let mut buf = Vec::new(); write_cmd.encode(&mut buf).expect("Failed to encode WriteCommand"); let cmd_bytes = Bytes::from(buf); // convert Vec to Bytes - crate::proto::common::EntryPayload { - payload: Some(crate::proto::common::entry_payload::Payload::Command( + d_engine_proto::common::EntryPayload { + payload: Some(d_engine_proto::common::entry_payload::Payload::Command( cmd_bytes, )), } diff --git a/d-engine-core/src/storage/storage_test.rs b/d-engine-core/src/storage/storage_test.rs index 37fef370..46b0273a 100644 --- a/d-engine-core/src/storage/storage_test.rs +++ b/d-engine-core/src/storage/storage_test.rs @@ -1,11 +1,11 @@ use super::*; use crate::convert::safe_kv; use crate::convert::skv; -use crate::proto::cluster::ClusterMembership; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::NodeStatus; -use crate::FOLLOWER; -use crate::LEARNER; +use d_engine_proto::cluster::ClusterMembership; +use d_engine_proto::cluster::NodeMeta; +use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeRole::Learner; +use d_engine_proto::common::NodeStatus; /// # Case 1: restart /// diff --git a/d-engine-proto/src/exts/client_ext.rs b/d-engine-proto/src/exts/client_ext.rs new file mode 100644 index 00000000..87c78aa8 --- /dev/null +++ b/d-engine-proto/src/exts/client_ext.rs @@ -0,0 +1,170 @@ +//! Extensions for protobuf-generated client types +//! +//! This module provides additional methods for protobuf-generated types +//! that are not automatically generated by the protobuf compiler. + +use crate::client::client_response::SuccessResult; +use crate::client::write_command; +use crate::client::ClientReadRequest; +use crate::client::ClientResult; +use bytes::Bytes; + +use crate::client::ClientResponse; +use crate::client::ReadConsistencyPolicy; +use crate::client::ReadResults; +use crate::client::WriteCommand; +use crate::error::ErrorCode; + +impl ClientReadRequest { + /// Checks if the consistency_policy field is present in the request + /// + /// Returns true if the client explicitly specified a consistency policy, + /// false if the field is absent (should use server default). + pub fn has_consistency_policy(&self) -> bool { + self.consistency_policy.is_some() + } + + /// Gets the consistency policy value safely + /// + /// Returns Some(policy) if present, None if field is absent. + /// Safer alternative that doesn't panic. + pub fn get_consistency_policy(&self) -> Option { + self.consistency_policy.and_then(|policy_i32| { + match policy_i32 { + x if x == ReadConsistencyPolicy::LeaseRead as i32 => { + Some(ReadConsistencyPolicy::LeaseRead) + } + x if x == ReadConsistencyPolicy::LinearizableRead as i32 => { + Some(ReadConsistencyPolicy::LinearizableRead) + } + _ => None, // Invalid value + } + }) + } +} + +impl WriteCommand { + /// Create write command for key-value pair + /// + /// # Parameters + /// - `key`: Byte array for storage key + /// - `value`: Byte array to be stored + pub fn insert( + key: impl Into, + value: impl Into, + ) -> Self { + let cmd = write_command::Insert { + key: key.into(), + value: value.into(), + }; + Self { + operation: Some(write_command::Operation::Insert(cmd)), + } + } + + /// Create deletion command for specified key + /// + /// # Parameters + /// - `key`: Byte array of key to delete + pub fn delete(key: impl Into) -> Self { + let cmd = write_command::Delete { key: key.into() }; + Self { + operation: Some(write_command::Operation::Delete(cmd)), + } + } +} + +impl ClientResponse { + /// Build success response for write operations + /// + /// # Returns + /// Response with NoError code and write confirmation + pub fn write_success() -> Self { + Self { + error: ErrorCode::Success as i32, + success_result: Some(SuccessResult::WriteAck(true)), + metadata: None, + } + } + + /// Check if the write operation was successful + /// + /// # Returns + /// - `true` if the response indicates a successful write operation + /// - `false` if the response indicates a failed write operation or is not a write response + pub fn is_write_success(&self) -> bool { + self.error == ErrorCode::Success as i32 + && matches!(self.success_result, Some(SuccessResult::WriteAck(true))) + } + + /// Build success response for read operations + /// + /// # Parameters + /// - `results`: Vector of retrieved key-value pairs + pub fn read_results(results: Vec) -> Self { + Self { + error: ErrorCode::Success as i32, + success_result: Some(SuccessResult::ReadData(ReadResults { results })), + metadata: None, + } + } + + /// Build generic error response for any operation type + /// + /// # Parameters + /// - `error_code`: Predefined client request error code + pub fn client_error(error_code: ErrorCode) -> Self { + Self { + error: error_code as i32, + success_result: None, + metadata: None, + } + } + + /// Check if this response indicates the leader's term is outdated + pub fn is_term_outdated(&self) -> bool { + ErrorCode::try_from(self.error).map(|e| e.is_term_outdated()).unwrap_or(false) + } + + /// Check if this response indicates a quorum timeout or failure to receive majority responses + pub fn is_quorum_timeout_or_failure(&self) -> bool { + ErrorCode::try_from(self.error) + .map(|e| e.is_quorum_timeout_or_failure()) + .unwrap_or(false) + } + + /// Check if this response indicates a failure to receive majority responses + pub fn is_propose_failure(&self) -> bool { + ErrorCode::try_from(self.error).map(|e| e.is_propose_failure()).unwrap_or(false) + } + + /// Check if this response indicates a a retry required + pub fn is_retry_required(&self) -> bool { + ErrorCode::try_from(self.error).map(|e| e.is_retry_required()).unwrap_or(false) + } +} + +impl ErrorCode { + /// Check if this error indicates the leader's term is outdated + pub fn is_term_outdated(&self) -> bool { + matches!(self, ErrorCode::TermOutdated) + } + + /// Check if this error indicates a quorum timeout or failure to receive majority responses + pub fn is_quorum_timeout_or_failure(&self) -> bool { + matches!( + self, + ErrorCode::ConnectionTimeout | ErrorCode::ProposeFailed | ErrorCode::ClusterUnavailable + ) + } + + /// Check if this error indicates a failure to receive majority responses + pub fn is_propose_failure(&self) -> bool { + matches!(self, ErrorCode::ProposeFailed) + } + + /// Check if this error indicates a retry required + pub fn is_retry_required(&self) -> bool { + matches!(self, ErrorCode::RetryRequired) + } +} diff --git a/d-engine-core/src/cluster_proto_extension.rs b/d-engine-proto/src/exts/cluster_ext.rs similarity index 56% rename from d-engine-core/src/cluster_proto_extension.rs rename to d-engine-proto/src/exts/cluster_ext.rs index 85c2e355..a0663c76 100644 --- a/d-engine-core/src/cluster_proto_extension.rs +++ b/d-engine-proto/src/exts/cluster_ext.rs @@ -1,64 +1,11 @@ -use d_engine_proto::{ - common::NodeStatus, error::ErrorCode, server::cluster::ClusterConfUpdateResponse, +use crate::{ + common::NodeStatus, server::cluster::cluster_conf_update_response::ErrorCode, + server::cluster::ClusterConfUpdateResponse, }; -pub trait ClusterConfUpdateResponseExt { +impl ClusterConfUpdateResponse { /// Generate a successful response (full success) - fn success( - node_id: u32, - term: u64, - version: u64, - ) -> Self; - - /// Generate a failed response (Stale leader term) - fn higher_term( - node_id: u32, - term: u64, - version: u64, - ) -> Self; - - /// Generate a failed response (Request sent to non-leader or an out-dated leader) - fn not_leader( - node_id: u32, - term: u64, - version: u64, - ) -> Self; - - /// Generate a failed response (Stale configuration version) - fn version_conflict( - node_id: u32, - term: u64, - version: u64, - ) -> Self; - - /// Generate a failed response (Malformed change request) - #[allow(unused)] - fn invalid_change( - node_id: u32, - term: u64, - version: u64, - ) -> Self; - - /// Generate a failed response (Server-side processing error) - fn internal_error( - node_id: u32, - term: u64, - version: u64, - ) -> Self; - - #[allow(unused)] - fn is_higher_term(&self) -> bool; -} - -pub trait NodeStatusExt { - fn is_promotable(&self) -> bool; - - fn is_i32_promotable(value: i32) -> bool; -} - -impl ClusterConfUpdateResponseExt for ClusterConfUpdateResponse { - /// Generate a successful response (full success) - fn success( + pub fn success( node_id: u32, term: u64, version: u64, @@ -73,7 +20,7 @@ impl ClusterConfUpdateResponseExt for ClusterConfUpdateResponse { } /// Generate a failed response (Stale leader term) - fn higher_term( + pub fn higher_term( node_id: u32, term: u64, version: u64, @@ -88,7 +35,7 @@ impl ClusterConfUpdateResponseExt for ClusterConfUpdateResponse { } /// Generate a failed response (Request sent to non-leader or an out-dated leader) - fn not_leader( + pub fn not_leader( node_id: u32, term: u64, version: u64, @@ -103,7 +50,7 @@ impl ClusterConfUpdateResponseExt for ClusterConfUpdateResponse { } /// Generate a failed response (Stale configuration version) - fn version_conflict( + pub fn version_conflict( node_id: u32, term: u64, version: u64, @@ -119,7 +66,7 @@ impl ClusterConfUpdateResponseExt for ClusterConfUpdateResponse { /// Generate a failed response (Malformed change request) #[allow(unused)] - fn invalid_change( + pub fn invalid_change( node_id: u32, term: u64, version: u64, @@ -134,7 +81,7 @@ impl ClusterConfUpdateResponseExt for ClusterConfUpdateResponse { } /// Generate a failed response (Server-side processing error) - fn internal_error( + pub fn internal_error( node_id: u32, term: u64, version: u64, @@ -149,17 +96,17 @@ impl ClusterConfUpdateResponseExt for ClusterConfUpdateResponse { } #[allow(unused)] - fn is_higher_term(&self) -> bool { + pub fn is_higher_term(&self) -> bool { self.error_code == >::into(ErrorCode::TermOutdated) } } -impl NodeStatusExt for NodeStatus { - fn is_promotable(&self) -> bool { +impl NodeStatus { + pub fn is_promotable(&self) -> bool { matches!(self, NodeStatus::Syncing) } - fn is_i32_promotable(value: i32) -> bool { + pub fn is_i32_promotable(value: i32) -> bool { matches!(value, v if v == (NodeStatus::Syncing as i32)) } } diff --git a/d-engine-proto/src/exts/common_ext.rs b/d-engine-proto/src/exts/common_ext.rs new file mode 100644 index 00000000..f972fc33 --- /dev/null +++ b/d-engine-proto/src/exts/common_ext.rs @@ -0,0 +1,25 @@ +use crate::common::{ + entry_payload::Payload, membership_change::Change, EntryPayload, MembershipChange, Noop, +}; +use bytes::Bytes; + +impl EntryPayload { + pub fn command(command: Bytes) -> Self { + Self { + payload: Some(Payload::Command(command)), + } + } + pub fn noop() -> Self { + Self { + payload: Some(Payload::Noop(Noop {})), + } + } + + pub fn config(change: Change) -> Self { + Self { + payload: Some(Payload::Config(MembershipChange { + change: Some(change), + })), + } + } +} diff --git a/d-engine-proto/src/exts/mod.rs b/d-engine-proto/src/exts/mod.rs new file mode 100644 index 00000000..805a8598 --- /dev/null +++ b/d-engine-proto/src/exts/mod.rs @@ -0,0 +1,9 @@ +mod client_ext; +mod cluster_ext; +mod common_ext; +mod replication_ext; + +pub use client_ext::*; +pub use cluster_ext::*; +pub use common_ext::*; +pub use replication_ext::*; diff --git a/d-engine-proto/src/exts/replication_ext.rs b/d-engine-proto/src/exts/replication_ext.rs new file mode 100644 index 00000000..f6ced053 --- /dev/null +++ b/d-engine-proto/src/exts/replication_ext.rs @@ -0,0 +1,76 @@ +use crate::{ + common::LogId, + server::replication::{ + append_entries_response, AppendEntriesResponse, ConflictResult, SuccessResult, + }, +}; + +impl AppendEntriesResponse { + /// Generate a successful response (full success) + pub fn success( + node_id: u32, + term: u64, + last_match: Option, + ) -> Self { + Self { + node_id, + term, + result: Some(append_entries_response::Result::Success(SuccessResult { + last_match, + })), + } + } + + /// Generate conflict response (with conflict details) + pub fn conflict( + node_id: u32, + term: u64, + conflict_term: Option, + conflict_index: Option, + ) -> Self { + Self { + node_id, + term, + result: Some(append_entries_response::Result::Conflict(ConflictResult { + conflict_term, + conflict_index, + })), + } + } + + /// Generate a conflict response (Higher term found) + pub fn higher_term( + node_id: u32, + term: u64, + ) -> Self { + Self { + node_id, + term, + result: Some(append_entries_response::Result::HigherTerm(term)), + } + } + + /// Check if it is a success response + pub fn is_success(&self) -> bool { + matches!( + &self.result, + Some(append_entries_response::Result::Success(_)) + ) + } + + /// Check if it is a conflict response + pub fn is_conflict(&self) -> bool { + matches!( + &self.result, + Some(append_entries_response::Result::Conflict(_conflict)) + ) + } + + /// Check if it is a response of a higher Term + pub fn is_higher_term(&self) -> bool { + matches!( + &self.result, + Some(append_entries_response::Result::HigherTerm(_)) + ) + } +} diff --git a/d-engine-proto/src/lib.rs b/d-engine-proto/src/lib.rs index 14f345e8..86d90e8f 100644 --- a/d-engine-proto/src/lib.rs +++ b/d-engine-proto/src/lib.rs @@ -31,3 +31,8 @@ pub mod server { pub mod client { include!("generated/d_engine.client.rs"); } + +mod exts; + +#[doc(hidden)] +pub use exts::*; diff --git a/d-engine-runtime/Cargo.toml b/d-engine-runtime/Cargo.toml new file mode 100644 index 00000000..e03bc13e --- /dev/null +++ b/d-engine-runtime/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "d-engine-server" +version = "0.2.0" +edition = "2024" + +[dependencies] +d-engine-proto = { workspace = true } +serde = { workspace = true } +tracing = { workspace = true } +tempfile = { workspace = true } +rocksdb = { version = "0.24.0", optional = true } +config = { version = "0.14.0", default-features = false, features = ["toml"] } + +[dev-dependencies] +serial_test = "3.2.0" +temp-env = "0.3.6" diff --git a/d-engine-server/src/lib.rs b/d-engine-runtime/src/lib.rs similarity index 100% rename from d-engine-server/src/lib.rs rename to d-engine-runtime/src/lib.rs diff --git a/d-engine-server/Cargo.toml b/d-engine-server/Cargo.toml deleted file mode 100644 index d60c77e5..00000000 --- a/d-engine-server/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "d-engine-server" -version = "0.1.0" -edition = "2024" - -[dependencies] -rocksdb = { version = "0.24.0", optional = true } diff --git a/src/network/mod.rs b/src/network/mod.rs index 879eeddb..50ab7170 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -1,10 +1,3 @@ -//! This module is the network abstraction layer with timeout-aware gRPC -//! implementation -//! -//! This module provides network communication facilities with configurable -//! timeout policies for distributed system operations. All network operations -//! are governed by timeout parameters defined in [`RaftConfig`] to ensure -//! system responsiveness. pub mod grpc; mod connection_cache; @@ -21,366 +14,3 @@ mod connection_cache_test; mod health_checker_test; #[cfg(test)] mod health_monitor_test; - -// Trait definition of the current module -// ----------------------------------------------------------------------------- -// Core model in Raft: Transport Definition -// -#[cfg(test)] -mod network_test; - -#[cfg(test)] -use mockall::automock; -use tonic::async_trait; - -use crate::proto::cluster::ClusterConfChangeRequest; -use crate::proto::cluster::ClusterConfUpdateResponse; -use crate::proto::election::VoteRequest; -use crate::proto::election::VoteResponse; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::replication::AppendEntriesResponse; -use crate::proto::storage::PurgeLogRequest; -use crate::proto::storage::PurgeLogResponse; -use crate::proto::storage::SnapshotChunk; -use crate::BackoffPolicy; -use crate::NetworkError; -use crate::Result; -use crate::RetryPolicies; -use crate::TypeConfig; - -// Define a structured return value -#[derive(Debug, Clone)] -pub(crate) struct AppendResults { - /// Whether a majority quorum is achieved (can be directly determined via - /// peer_updates) - pub commit_quorum_achieved: bool, - /// Updates to each peer's match_index and next_index - pub peer_updates: HashMap, - /// Learner log catch-up progress - pub learner_progress: HashMap>, -} - -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct PeerUpdate { - pub match_index: Option, - pub next_index: u64, - /// if peer response success - pub success: bool, -} - -impl PeerUpdate { - #[allow(unused)] - pub fn success( - match_index: u64, - next_index: u64, - ) -> Self { - PeerUpdate { - match_index: Some(match_index), - next_index, - success: true, - } - } - - #[allow(unused)] - pub fn failed() -> Self { - Self { - match_index: None, - next_index: 1, - success: false, - } - } -} -#[derive(Debug)] -pub struct AppendResult { - pub peer_ids: HashSet, - pub responses: Vec>, -} -#[derive(Debug)] -pub struct VoteResult { - pub peer_ids: HashSet, - pub responses: Vec>, -} -#[allow(dead_code)] -#[derive(Debug)] -pub struct ClusterUpdateResult { - pub peer_ids: HashSet, - pub responses: Vec>, -} - -#[cfg_attr(test, automock)] -#[async_trait] -pub trait Transport: Send + Sync + 'static -where - T: TypeConfig, -{ - /// Propagates cluster configuration changes to voting members using Raft's joint consensus. - /// - /// # Protocol - /// - Implements membership change protocol from Raft §6 - /// - Leader-exclusive operation - /// - Automatically filters self-references and duplicates - /// - /// # Parameters - /// - `req`: Configuration change details with transition state - /// - `retry`: Network retry policy with exponential backoff - /// - `membership`: Cluster membership for channel resolution - /// - /// # Errors - /// - `NetworkError::EmptyPeerList` if no peers provided - /// - `NetworkError::TaskFailed` for background execution failures - /// - `ConsensusError::NotLeader` if executed by non-leader - /// - /// # Implementation - /// - Uses compressed gRPC streams for efficiency - /// - Maintains response order matching input peers - /// - Concurrent request processing with ordered aggregation - #[allow(dead_code)] - async fn send_cluster_update( - &self, - req: ClusterConfChangeRequest, - retry: &RetryPolicies, - membership: std::sync::Arc>, - ) -> Result; - - /// Replicates log entries to followers and learners. - /// - /// # Protocol - /// - Implements log replication from Raft §5.3 - /// - Leader-exclusive operation - /// - Handles log consistency checks automatically - /// - /// # Parameters - /// - `requests`: Vector of (peer_id, AppendEntriesRequest) - /// - `retry`: Network retry configuration - /// - `membership`: Cluster membership for channel resolution - /// - `response_compress_enabled`: Enable compression for replication responses - /// - /// # Returns - /// - On success: `Ok(AppendResult)` containing aggregated responses - /// - On failure: `Err(NetworkError)` for unrecoverable errors - /// - /// ## **Error Conditions**: Top-level `Err` is returned ONLY when: - /// - Input `requests_with_peer_address` is empty (`NetworkError::EmptyPeerList`) - /// - Critical failures prevent spawning async tasks (not shown in current impl) - /// - /// # Errors - /// - `NetworkError::EmptyPeerList` for empty input - /// - `NetworkError::TaskFailed` for partial execution failures - /// - /// # Guarantees - /// - At-least-once delivery semantics - /// - Automatic deduplication of peer entries - /// - Non-blocking error handling - async fn send_append_requests( - &self, - requests: Vec<(u32, AppendEntriesRequest)>, - retry: &RetryPolicies, - membership: std::sync::Arc>, - response_compress_enabled: bool, - ) -> Result; - - /// Initiates leader election by requesting votes from cluster peers. - /// - /// # Protocol - /// - Implements leader election from Raft §5.2 - /// - Candidate-exclusive operation - /// - Validates log completeness requirements - /// - /// # Parameters - /// - `req`: Election metadata with candidate's term and log state - /// - `retry`: Election-specific retry strategy - /// - `membership`: Cluster membership for channel resolution - /// - /// # Errors - /// - `NetworkError::EmptyPeerList` for empty peer list - /// - `NetworkError::TaskFailed` for RPC execution failures - /// - /// # Safety - /// - Automatic term validation in responses - /// - Strict candidate state enforcement - /// - Non-blocking partial failure handling - async fn send_vote_requests( - &self, - req: VoteRequest, - retry: &RetryPolicies, - membership: std::sync::Arc>, - ) -> Result; - - /// Orchestrates log compaction across cluster peers after snapshot creation. - /// - /// # Protocol - /// - Implements log truncation from Raft §7 - /// - Leader-exclusive operation - /// - Requires valid snapshot checksum - /// - /// # Parameters - /// - `req`: Snapshot metadata with truncation index - /// - `retry`: Purge-specific retry configuration - /// - `membership`: Cluster membership for channel resolution - /// - /// # Errors - /// - `NetworkError::EmptyPeerList` for empty peer list - /// - `NetworkError::TaskFailed` for background execution errors - /// - /// # Guarantees - /// - At-least-once delivery - /// - Automatic progress tracking - /// - Crash-safe persistence requirements - async fn send_purge_requests( - &self, - req: PurgeLogRequest, - retry: &RetryPolicies, - membership: std::sync::Arc>, - ) -> Result>>; - - /// Initiates cluster join process for a learner node - /// - /// # Protocol - /// - Implements cluster join protocol from Raft §6 - /// - Learner-exclusive operation - /// - Requires leader connection - /// - /// # Parameters - /// - `leader_channel`: Pre-established gRPC channel to cluster leader - /// - `request`: Join request with node metadata - /// - `retry`: Join-specific retry configuration - /// - `membership`: Cluster membership for channel resolution - /// - /// # Errors - /// - NetworkError::JoinFailed: On unrecoverable join failure - /// - NetworkError::NotLeader: If contacted node isn't leader - /// - /// # Guarantees - /// - At-least-once delivery - /// - Automatic leader discovery - /// - Idempotent operation - async fn join_cluster( - &self, - leader_id: u32, - request: crate::proto::cluster::JoinRequest, - retry: BackoffPolicy, - membership: std::sync::Arc>, - ) -> Result; - - /// Discovers current cluster leader - /// - /// # Protocol - /// - Broadcast-based leader discovery - /// - Handles redirection to current leader - /// - /// # Parameters - /// - `bootstrap_endpoints`: Initial cluster endpoints - /// - `request`: Discovery request with node metadata - /// - /// # Errors - /// - `NetworkError::DiscoveryTimeout`: When no response received - async fn discover_leader( - &self, - request: crate::proto::cluster::LeaderDiscoveryRequest, - rpc_enable_compression: bool, - membership: std::sync::Arc>, - ) -> Result>; - - /// Requests and streams a snapshot from the current leader. - /// - /// # Parameters - /// - `leader_id`: Current leader node ID - /// - `retry`: Retry configuration (currently unused in implementation) - /// - `membership`: Cluster membership for channel resolution - /// - /// # Returns - /// Streaming of snapshot chunks from the leader - /// - /// # Errors - /// Returns `NetworkError` if: - /// - Connection to leader fails - /// - gRPC call fails - async fn request_snapshot_from_leader( - &self, - leader_id: u32, - ack_tx: tokio::sync::mpsc::Receiver, - retry: &crate::InstallSnapshotBackoffPolicy, - membership: std::sync::Arc>, - ) -> Result>>; -} - -// Module level utils -// ----------------------------------------------------------------------------- -use std::collections::HashMap; -use std::collections::HashSet; -use std::time::Duration; - -use tokio::time::sleep; -use tokio::time::timeout; -use tonic::Code; -use tracing::debug; -use tracing::warn; - -use crate::Error; - -/// As soon as task has return we should return from this function -pub(crate) async fn grpc_task_with_timeout_and_exponential_backoff( - task_name: &'static str, - mut task: F, - policy: BackoffPolicy, -) -> std::result::Result, Error> -where - F: FnMut() -> T, - T: std::future::Future, tonic::Status>> - + Send - + 'static, -{ - // let max_retries = 5; - let mut retries = 0; - let mut current_delay = Duration::from_millis(policy.base_delay_ms); - let timeout_duration = Duration::from_millis(policy.timeout_ms); - let max_delay = Duration::from_millis(policy.max_delay_ms); - let max_retries = policy.max_retries; - - let mut last_error = - NetworkError::TaskBackoffFailed("Task failed after max retries".to_string()); - while retries < max_retries { - debug!("[{task_name}] Attempt {} of {}", retries + 1, max_retries); - match timeout(timeout_duration, task()).await { - Ok(Ok(r)) => { - return Ok(r); // Exit on success - } - Ok(Err(status)) => { - last_error = match status.code() { - Code::Unavailable => { - warn!("[{task_name}] Service unavailable: {}", status.message()); - NetworkError::ServiceUnavailable(format!( - "Service unavailable: {}", - status.message() - )) - } - _ => { - warn!("[{task_name}] RPC error: {}", status); - NetworkError::TonicStatusError(Box::new(status)) - } - }; - } - Err(_e) => { - warn!("[{task_name}] Task timed out after {:?}", timeout_duration); - last_error = NetworkError::RetryTimeoutError(timeout_duration); - } - }; - - if retries < max_retries - 1 { - debug!("[{task_name}] Retrying in {:?}...", current_delay); - sleep(current_delay).await; - - // Exponential backoff (double the delay each time) - current_delay = (current_delay * 2).min(max_delay); - } else { - warn!("[{task_name}] Task failed after {} retries", retries); - //bug: no need to return if the it is not a business logic error - // return Err(Error::RetryTaskFailed("Task failed after max - // retries".to_string())); // Return the last error after max - // retries - } - retries += 1; - } - warn!("[{task_name}] Task failed after {} retries", max_retries); - Err(last_error.into()) // Fallback error message if no task returns Ok -} diff --git a/src/node/type_config/mod.rs b/src/node/type_config/mod.rs index c35c1dbd..1ebc4e3e 100644 --- a/src/node/type_config/mod.rs +++ b/src/node/type_config/mod.rs @@ -1,6 +1,4 @@ -mod maybe_clone_oneshot; mod raft_type_config; -pub(crate) use maybe_clone_oneshot::*; #[doc(hidden)] pub use raft_type_config::*; From 7e9d76631b65605e9c0742f0727274fdcbe5bf1a Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Wed, 22 Oct 2025 19:25:23 +0800 Subject: [PATCH 05/26] feat #167: d-engine-runtime and d-engine-core workspace structure is ready. And make d-engine-core/test_utils as feature. --- Cargo.lock | 452 +++++++++++++++++- Cargo.toml | 11 +- d-engine-client/Cargo.toml | 2 +- d-engine-core/Cargo.toml | 22 + .../commit_handler/default_commit_handler.rs | 2 +- .../default_commit_handler_test.rs | 69 +-- d-engine-core/src/config/config_test.rs | 4 +- d-engine-core/src/config/raft.rs | 8 + .../src/election/election_handler_test.rs | 2 +- d-engine-core/src/event.rs | 8 +- d-engine-core/src/lib.rs | 17 +- d-engine-core/src/maybe_clone_oneshot.rs | 6 +- d-engine-core/src/membership.rs | 35 +- .../network}/backgroup_snapshot_transfer.rs | 14 +- .../backgroup_snapshot_transfer_test.rs | 12 +- d-engine-core/src/network/mod.rs | 7 +- d-engine-core/src/raft.rs | 6 +- .../src/raft_role/follower_state_test.rs | 7 +- d-engine-core/src/raft_role/leader_state.rs | 6 +- .../src/raft_role/leader_state_test.rs | 70 ++- d-engine-core/src/raft_role/learner_state.rs | 4 +- .../src/raft_role/learner_state_test.rs | 2 +- d-engine-core/src/raft_test.rs | 15 +- d-engine-core/src/replication/mod.rs | 2 - .../src/replication/replication_handler.rs | 2 +- .../replication/replication_handler_test.rs | 112 +++-- .../default_state_machine_handler.rs | 21 +- .../default_state_machine_handler_test.rs | 378 ++++++++------- .../src/state_machine_handler/mod.rs | 1 + .../snapshot_policy/composite_test.rs | 11 +- .../snapshot_policy/log_size_test.rs | 22 +- .../snapshot_policy/time_based_test.rs | 12 +- .../buffered/buffered_raft_log_test.rs | 3 +- d-engine-core/src/storage/raft_log.rs | 35 -- d-engine-core/src/storage/state_machine.rs | 21 +- d-engine-core/src/storage/storage_engine.rs | 2 +- .../src/storage/storage_engine_test.rs | 2 +- .../src}/test_utils/common.rs | 7 +- .../src}/test_utils/entry_builder.rs | 0 .../src}/test_utils/integration/mod.rs | 22 +- .../src}/test_utils/integration/snapshot.rs | 0 .../src/test_utils/mock/mock_raft_builder.rs | 420 ++++++++++++++++ .../src}/test_utils/mock/mock_rpc.rs | 0 .../src}/test_utils/mock/mock_rpc_service.rs | 22 +- .../test_utils/mock/mock_storage_engine.rs | 0 .../src}/test_utils/mock/mod.rs | 39 +- .../src}/test_utils/mock_type_config.rs | 0 {src => d-engine-core/src}/test_utils/mod.rs | 0 d-engine-core/src/utils/cluster.rs | 8 +- {src => d-engine-core/src}/utils/convert.rs | 0 .../src}/utils/convert_test.rs | 0 {src => d-engine-core/src}/utils/file_io.rs | 0 .../src}/utils/file_io_test.rs | 0 d-engine-core/src/utils/mod.rs | 24 +- {src => d-engine-core/src}/utils/stream.rs | 0 .../src}/utils/stream_test.rs | 0 {src => d-engine-core/src}/utils/time.rs | 0 {src => d-engine-core/src}/utils/time_test.rs | 0 d-engine-proto/src/exts/common_ext.rs | 27 ++ d-engine-proto/src/exts/mod.rs | 2 + d-engine-proto/src/exts/storage_ext.rs | 24 + d-engine-runtime/Cargo.toml | 18 + d-engine-runtime/src/lib.rs | 23 + .../src}/membership/membership_guard.rs | 6 +- .../src}/membership/membership_guard_test.rs | 8 +- .../src}/membership/mod.rs | 0 .../src}/membership/raft_membership.rs | 93 ++-- .../src}/membership/raft_membership_test.rs | 224 ++++----- .../src}/network/connection_cache.rs | 4 +- .../src}/network/connection_cache_test.rs | 0 .../src}/network/grpc/grpc_raft_service.rs | 52 +- .../network/grpc/grpc_raft_service_test.rs | 112 +++-- .../src}/network/grpc/grpc_transport.rs | 46 +- .../src}/network/grpc/grpc_transport_test.rs | 107 +++-- .../src}/network/grpc/mod.rs | 25 +- .../src}/network/health_checker.rs | 10 +- .../src}/network/health_checker_test.rs | 0 .../src}/network/health_monitor.rs | 0 .../src}/network/health_monitor_test.rs | 0 {src => d-engine-runtime/src}/network/mod.rs | 0 .../src}/network/network_test.rs | 56 ++- {src => d-engine-runtime/src}/node/builder.rs | 22 +- .../src}/node/builder_test.rs | 0 {src => d-engine-runtime/src}/node/mod.rs | 36 +- .../src}/node/node_test.rs | 34 +- .../src}/node/type_config/mod.rs | 0 .../src}/node/type_config/raft_type_config.rs | 4 +- .../storage/adaptors/file/file_engine_test.rs | 0 .../adaptors/file/file_state_machine.rs | 16 +- .../adaptors/file/file_state_machine_test.rs | 12 +- .../adaptors/file/file_storage_engine.rs | 8 +- .../adaptors/file/file_storage_engine_test.rs | 8 +- .../src}/storage/adaptors/file/mod.rs | 0 .../src}/storage/adaptors/mod.rs | 0 .../src}/storage/adaptors/rocksdb/mod.rs | 0 .../adaptors/rocksdb/rocksdb_engine_test.rs | 8 +- .../adaptors/rocksdb/rocksdb_state_machine.rs | 20 +- .../rocksdb/rocksdb_storage_engine.rs | 8 +- {src => d-engine-runtime/src}/storage/mod.rs | 0 .../src/test_utils/mock/mock_node_builder.rs | 0 d-engine-runtime/src/test_utils/mock/mod.rs | 39 ++ d-engine-runtime/src/test_utils/mod.rs | 3 + .../src}/utils/async_task.rs | 4 +- .../src}/utils/async_task_test.rs | 0 .../src}/utils/cluster.rs | 18 +- {src => d-engine-runtime/src}/utils/mod.rs | 12 - {src => d-engine-runtime/src}/utils/net.rs | 0 .../src}/utils/net_test.rs | 0 .../src}/utils/scoped_timer.rs | 0 .../src}/utils/utils_test.rs | 6 +- src/constants.rs | 2 - src/lib.rs | 42 -- 112 files changed, 2035 insertions(+), 1051 deletions(-) rename {src/network/grpc => d-engine-core/src/network}/backgroup_snapshot_transfer.rs (98%) rename {src/network/grpc => d-engine-core/src/network}/backgroup_snapshot_transfer_test.rs (99%) rename {src => d-engine-core/src}/test_utils/common.rs (98%) rename {src => d-engine-core/src}/test_utils/entry_builder.rs (100%) rename {src => d-engine-core/src}/test_utils/integration/mod.rs (97%) rename {src => d-engine-core/src}/test_utils/integration/snapshot.rs (100%) create mode 100644 d-engine-core/src/test_utils/mock/mock_raft_builder.rs rename {src => d-engine-core/src}/test_utils/mock/mock_rpc.rs (100%) rename {src => d-engine-core/src}/test_utils/mock/mock_rpc_service.rs (98%) rename {src => d-engine-core/src}/test_utils/mock/mock_storage_engine.rs (100%) rename {src => d-engine-core/src}/test_utils/mock/mod.rs (69%) rename {src => d-engine-core/src}/test_utils/mock_type_config.rs (100%) rename {src => d-engine-core/src}/test_utils/mod.rs (100%) rename {src => d-engine-core/src}/utils/convert.rs (100%) rename {src => d-engine-core/src}/utils/convert_test.rs (100%) rename {src => d-engine-core/src}/utils/file_io.rs (100%) rename {src => d-engine-core/src}/utils/file_io_test.rs (100%) rename {src => d-engine-core/src}/utils/stream.rs (100%) rename {src => d-engine-core/src}/utils/stream_test.rs (100%) rename {src => d-engine-core/src}/utils/time.rs (100%) rename {src => d-engine-core/src}/utils/time_test.rs (100%) create mode 100644 d-engine-proto/src/exts/storage_ext.rs rename {src => d-engine-runtime/src}/membership/membership_guard.rs (95%) rename {src => d-engine-runtime/src}/membership/membership_guard_test.rs (97%) rename {src => d-engine-runtime/src}/membership/mod.rs (100%) rename {src => d-engine-runtime/src}/membership/raft_membership.rs (92%) rename {src => d-engine-runtime/src}/membership/raft_membership_test.rs (89%) rename {src => d-engine-runtime/src}/network/connection_cache.rs (98%) rename {src => d-engine-runtime/src}/network/connection_cache_test.rs (100%) rename {src => d-engine-runtime/src}/network/grpc/grpc_raft_service.rs (91%) rename {src => d-engine-runtime/src}/network/grpc/grpc_raft_service_test.rs (79%) rename {src => d-engine-runtime/src}/network/grpc/grpc_transport.rs (95%) rename {src => d-engine-runtime/src}/network/grpc/grpc_transport_test.rs (92%) rename {src => d-engine-runtime/src}/network/grpc/mod.rs (91%) rename {src => d-engine-runtime/src}/network/health_checker.rs (98%) rename {src => d-engine-runtime/src}/network/health_checker_test.rs (100%) rename {src => d-engine-runtime/src}/network/health_monitor.rs (100%) rename {src => d-engine-runtime/src}/network/health_monitor_test.rs (100%) rename {src => d-engine-runtime/src}/network/mod.rs (100%) rename {src => d-engine-runtime/src}/network/network_test.rs (54%) rename {src => d-engine-runtime/src}/node/builder.rs (99%) rename {src => d-engine-runtime/src}/node/builder_test.rs (100%) rename {src => d-engine-runtime/src}/node/mod.rs (84%) rename {src => d-engine-runtime/src}/node/node_test.rs (95%) rename {src => d-engine-runtime/src}/node/type_config/mod.rs (100%) rename {src => d-engine-runtime/src}/node/type_config/raft_type_config.rs (97%) rename {src => d-engine-runtime/src}/storage/adaptors/file/file_engine_test.rs (100%) rename {src => d-engine-runtime/src}/storage/adaptors/file/file_state_machine.rs (98%) rename {src => d-engine-runtime/src}/storage/adaptors/file/file_state_machine_test.rs (90%) rename {src => d-engine-runtime/src}/storage/adaptors/file/file_storage_engine.rs (99%) rename {src => d-engine-runtime/src}/storage/adaptors/file/file_storage_engine_test.rs (98%) rename {src => d-engine-runtime/src}/storage/adaptors/file/mod.rs (100%) rename {src => d-engine-runtime/src}/storage/adaptors/mod.rs (100%) rename {src => d-engine-runtime/src}/storage/adaptors/rocksdb/mod.rs (100%) rename {src => d-engine-runtime/src}/storage/adaptors/rocksdb/rocksdb_engine_test.rs (97%) rename {src => d-engine-runtime/src}/storage/adaptors/rocksdb/rocksdb_state_machine.rs (97%) rename {src => d-engine-runtime/src}/storage/adaptors/rocksdb/rocksdb_storage_engine.rs (99%) rename {src => d-engine-runtime/src}/storage/mod.rs (100%) rename src/test_utils/mock/mock_builder.rs => d-engine-runtime/src/test_utils/mock/mock_node_builder.rs (100%) create mode 100644 d-engine-runtime/src/test_utils/mock/mod.rs create mode 100644 d-engine-runtime/src/test_utils/mod.rs rename {src => d-engine-runtime/src}/utils/async_task.rs (97%) rename {src => d-engine-runtime/src}/utils/async_task_test.rs (100%) rename {src => d-engine-runtime/src}/utils/cluster.rs (85%) rename {src => d-engine-runtime/src}/utils/mod.rs (74%) rename {src => d-engine-runtime/src}/utils/net.rs (100%) rename {src => d-engine-runtime/src}/utils/net_test.rs (100%) rename {src => d-engine-runtime/src}/utils/scoped_timer.rs (100%) rename {src => d-engine-runtime/src}/utils/utils_test.rs (97%) delete mode 100644 src/constants.rs delete mode 100644 src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 411d8dec..459ee904 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anstyle" version = "1.0.11" @@ -56,6 +62,19 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "async-compression" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" +dependencies = [ + "compression-codecs", + "compression-core", + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -184,7 +203,7 @@ version = "0.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f" dependencies = [ - "bitflags", + "bitflags 2.9.3", "cexpr", "clang-sys", "itertools 0.13.0", @@ -196,12 +215,33 @@ dependencies = [ "syn", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "bytes" version = "1.10.1" @@ -247,6 +287,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "clang-sys" version = "1.8.1" @@ -258,6 +304,23 @@ dependencies = [ "libloading", ] +[[package]] +name = "compression-codecs" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" + [[package]] name = "config" version = "0.14.1" @@ -270,6 +333,15 @@ dependencies = [ "toml", ] +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -279,6 +351,82 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-skiplist" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "d-engine-client" version = "0.2.0" @@ -302,25 +450,42 @@ dependencies = [ name = "d-engine-core" version = "0.1.0" dependencies = [ + "async-compression", + "async-stream", "bincode", "bytes", "config", + "crc32fast", + "crossbeam", + "crossbeam-skiplist", "d-engine-proto", "dashmap", "futures", + "http-body", + "http-body-util", + "lru", + "memmap2", "metrics", "mockall", "nanoid", + "nix", + "parking_lot", "prost", + "rand", "serde", "serial_test", + "sha2", "temp-env", "tempfile", "thiserror", "tokio", + "tokio-stream", + "tokio-tar", "tonic", + "tonic-health", "tracing", "tracing-test", + "uuid", ] [[package]] @@ -344,14 +509,26 @@ dependencies = [ name = "d-engine-server" version = "0.2.0" dependencies = [ + "arc-swap", + "bytes", "config", + "d-engine-core", "d-engine-proto", + "dashmap", + "futures", + "prost", + "rcgen", "rocksdb", "serde", "serial_test", "temp-env", "tempfile", + "tokio", + "tokio-util", + "tonic", + "tonic-health", "tracing", + "tracing-test", ] [[package]] @@ -376,6 +553,16 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "downcast" version = "0.11.0" @@ -410,6 +597,18 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "filetime" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.60.2", +] + [[package]] name = "fixedbitset" version = "0.5.7" @@ -432,6 +631,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "fragile" version = "2.0.1" @@ -527,6 +732,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -598,6 +813,11 @@ name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "heck" @@ -734,7 +954,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags", + "bitflags 2.9.3", "cfg-if", "libc", ] @@ -773,6 +993,16 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -795,6 +1025,17 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags 2.9.3", + "libc", + "redox_syscall 0.5.18", +] + [[package]] name = "librocksdb-sys" version = "0.17.3+10.4.2" @@ -842,6 +1083,15 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "lru" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0281c2e25e62316a5c9d98f2d2e9e95a37841afdaf4383c177dbb5c1dfab0568" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "lz4-sys" version = "1.11.1+lz4-1.10.0" @@ -873,6 +1123,15 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "memmap2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +dependencies = [ + "libc", +] + [[package]] name = "metrics" version = "0.24.2" @@ -957,6 +1216,18 @@ dependencies = [ "rand", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.3", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1024,7 +1295,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link 0.2.1", ] @@ -1035,6 +1306,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1252,13 +1533,35 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.9.3", ] [[package]] @@ -1332,7 +1635,7 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags", + "bitflags 2.9.3", "errno", "libc", "linux-raw-sys", @@ -1412,18 +1715,28 @@ checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1464,6 +1777,17 @@ dependencies = [ "syn", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1684,6 +2008,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tar" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" +dependencies = [ + "filetime", + "futures-core", + "libc", + "redox_syscall 0.3.5", + "tokio", + "tokio-stream", + "xattr", +] + [[package]] name = "tokio-util" version = "0.7.16" @@ -1932,6 +2271,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -1944,6 +2289,17 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -1998,6 +2354,65 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + [[package]] name = "windows-link" version = "0.1.3" @@ -2181,6 +2596,25 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "zerocopy" version = "0.8.26" diff --git a/Cargo.toml b/Cargo.toml index fe1b3235..d7b98bd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,8 +46,6 @@ prost = { version = "0.13", default-features = false } bincode = "1.3" tonic = { version = "0.12.3", features = ["gzip", "tls"] } tonic-health = "0.12.3" -# certification -rcgen = { version = "0.13", features = ["pem"] } # compress flate2 = { version = "1.0", default-features = false, features = ["zlib"] } # Async @@ -58,8 +56,7 @@ tokio = { version = "1.4", features = [ "fs", "sync", ] } -# use tokio_util::sync::CancellationToken; -tokio-util = "0.7.11" + futures = "0.3.30" @@ -84,17 +81,13 @@ async-stream = "0.3.6" # Compress/decompress data stream async-compression = { version = "0.4", features = ["tokio", "gzip"] } -http-body = "1.0" -http-body-util = "0.1.3" + memmap2 = "0.9.5" -lru = "0.15.0" # Cargo build performance optimization ring = { version = "=0.17.14", features = ["std"] } # TODO: to replace dashmap -crossbeam-skiplist = "0.1" -crossbeam = "0.8" # [dev-dependencies] # mockall = "0.12.1" diff --git a/d-engine-client/Cargo.toml b/d-engine-client/Cargo.toml index ca9e9a24..6ae58f0a 100644 --- a/d-engine-client/Cargo.toml +++ b/d-engine-client/Cargo.toml @@ -15,7 +15,7 @@ serde = { workspace = true } rand = { workspace = true } arc-swap = "1.7.1" futures = { workspace = true } -tokio-stream = { workspace = true } +tokio-stream = "0.1.16" [dev-dependencies] mockall = "0.12.1" diff --git a/d-engine-core/Cargo.toml b/d-engine-core/Cargo.toml index 85b051e6..aca1a157 100644 --- a/d-engine-core/Cargo.toml +++ b/d-engine-core/Cargo.toml @@ -3,6 +3,9 @@ name = "d-engine-core" version = "0.1.0" edition = "2024" +[features] +test-utils = [] + [dependencies] d-engine-proto = { workspace = true } @@ -16,6 +19,7 @@ prost = { workspace = true } bincode = "1.3" thiserror = "1.0" nanoid = "0.4.0" +sha2 = "0.10.9" # using Dashset dashmap = "5.5.3" tempfile = "3.19.1" @@ -24,9 +28,27 @@ config = { version = "0.14.0", default-features = false, features = ["toml"] } tokio-stream = "0.1.16" # Packing/unpacking TAR archives tokio-tar = "0.3" +rand = "0.8.5" +lru = "0.15.0" +memmap2 = "0.9.5" +crc32fast = "1.4.2" +# used in stream +http-body = "1.0" +http-body-util = "0.1.3" +crossbeam-skiplist = "0.1" +crossbeam = "0.8" + +# Compress/decompress data stream +async-compression = { version = "0.4", features = ["tokio", "gzip"] } +async-stream = "0.3.6" [dev-dependencies] mockall = "0.12.1" tracing-test = "0.2" serial_test = "3.2.0" temp-env = "0.3.6" +tokio = { version = "1", features = ["test-util", "process"] } +parking_lot = "0.12.3" +uuid = { version = "1", features = ["v4"] } +tonic-health = "0.12.3" +nix = { version = "0.30.1", features = ["fs"] } diff --git a/d-engine-core/src/commit_handler/default_commit_handler.rs b/d-engine-core/src/commit_handler/default_commit_handler.rs index 187b70eb..909e07ac 100644 --- a/d-engine-core/src/commit_handler/default_commit_handler.rs +++ b/d-engine-core/src/commit_handler/default_commit_handler.rs @@ -17,12 +17,12 @@ use crate::RaftEvent; use crate::RaftLog; use crate::RaftNodeConfig; use crate::Result; -use crate::ScopedTimer; use crate::StateMachineHandler; use crate::TypeConfig; use crate::alias::MOF; use crate::alias::ROF; use crate::alias::SMHOF; +use crate::scoped_timer::ScopedTimer; use d_engine_proto::common::Entry; use d_engine_proto::common::entry_payload::Payload; diff --git a/d-engine-core/src/commit_handler/default_commit_handler_test.rs b/d-engine-core/src/commit_handler/default_commit_handler_test.rs index b9239f1b..9bf15702 100644 --- a/d-engine-core/src/commit_handler/default_commit_handler_test.rs +++ b/d-engine-core/src/commit_handler/default_commit_handler_test.rs @@ -12,6 +12,7 @@ use tokio::time::{self}; use super::CommitHandler; use super::CommitHandlerDependencies; use super::DefaultCommitHandler; +use crate::CommitHandlerConfig; use crate::Error; use crate::MockMembership; use crate::MockRaftLog; @@ -23,7 +24,6 @@ use crate::RaftNodeConfig; use crate::Result; use crate::test_utils::MockTypeConfig; use crate::test_utils::generate_insert_commands; -use crate::{CommitHandlerConfig, EntryPayloadExt}; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; use d_engine_proto::common::LogId; @@ -313,7 +313,14 @@ fn setup( }, ..Default::default() }; - DefaultCommitHandler::::new(1, Leader, 1, deps, Arc::new(config), new_commit_rx) + DefaultCommitHandler::::new( + 1, + Leader.into(), + 1, + deps, + Arc::new(config), + new_commit_rx, + ) } /// # Case 1: interval_uses_correct_duration @@ -427,7 +434,7 @@ mod run_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -441,7 +448,7 @@ mod run_test { // Send commits to trigger processing for i in 1..=4 { - harness.send_commit(i, Leader).await; + harness.send_commit(i, Leader.into()).await; } // Verify snapshot triggered @@ -464,7 +471,7 @@ mod run_test { ); let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -476,8 +483,8 @@ mod run_test { ); harness.run_handler().await; - harness.send_commit(1, Leader).await; - harness.send_commit(2, Follower).await; + harness.send_commit(1, Leader.into()).await; + harness.send_commit(2, Follower.into()).await; // Should not process second command time::sleep(Duration::from_millis(50)).await; @@ -497,7 +504,7 @@ mod run_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -509,7 +516,7 @@ mod run_test { ); harness.run_handler().await; - harness.send_commit(1, Leader).await; + harness.send_commit(1, Leader.into()).await; time::sleep(Duration::from_millis(50)).await; harness.shutdown_tx.send(()).unwrap(); harness.handle.unwrap().await.unwrap(); @@ -526,7 +533,7 @@ mod run_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -540,7 +547,7 @@ mod run_test { // Send all commits at once for i in 1..=1000 { - harness.send_commit(i, Leader).await; + harness.send_commit(i, Leader.into()).await; } println!("Sent all commits"); @@ -576,7 +583,7 @@ mod run_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -590,7 +597,7 @@ mod run_test { // Send commits to trigger processing for i in 1..=2 { - harness.send_commit(i, Leader).await; + harness.send_commit(i, Leader.into()).await; } tokio::time::advance(Duration::from_millis(3)).await; // Clean shutdown @@ -629,7 +636,7 @@ mod run_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -642,7 +649,7 @@ mod run_test { harness.run_handler().await; for i in 1..=batch_thresold { - harness.send_commit(i, Leader).await; + harness.send_commit(i, Leader.into()).await; } tokio::time::advance(Duration::from_millis(2)).await; tokio::time::sleep(Duration::from_millis(2)).await; @@ -682,7 +689,7 @@ mod run_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -695,7 +702,7 @@ mod run_test { harness.run_handler().await; for i in 1..=(batch_thresold - 10) { - harness.send_commit(i, Leader).await; + harness.send_commit(i, Leader.into()).await; } tokio::time::advance(Duration::from_millis(2)).await; tokio::time::sleep(Duration::from_millis(2)).await; @@ -735,7 +742,7 @@ mod run_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -748,7 +755,7 @@ mod run_test { harness.run_handler().await; for i in 1..=batch_thresold { - harness.send_commit(i, Leader).await; + harness.send_commit(i, Leader.into()).await; } tokio::time::advance(Duration::from_millis(2)).await; tokio::time::sleep(Duration::from_millis(2)).await; @@ -841,7 +848,7 @@ mod process_batch_test { async fn processes_empty_batch_successfully() { let last_applied = 0; let mut harness = setup_harness( - Leader, + Leader.into(), 1, vec![], last_applied as u64, @@ -868,7 +875,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -900,7 +907,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -940,7 +947,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -971,7 +978,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -999,7 +1006,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -1025,7 +1032,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -1047,7 +1054,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -1082,7 +1089,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -1108,7 +1115,7 @@ mod process_batch_test { let entries = build_entries(r, 1); let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -1142,7 +1149,7 @@ mod process_batch_test { let order_capture = process_order.clone(); let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, @@ -1191,7 +1198,7 @@ mod process_batch_test { let last_applied = entries.len(); let mut harness = setup_harness( - Leader, + Leader.into(), 1, entries, last_applied as u64, diff --git a/d-engine-core/src/config/config_test.rs b/d-engine-core/src/config/config_test.rs index 01ba914b..fd325fc1 100644 --- a/d-engine-core/src/config/config_test.rs +++ b/d-engine-core/src/config/config_test.rs @@ -6,7 +6,9 @@ use super::*; fn cleanup_all_raft_env_vars() { for (key, _) in std::env::vars() { if key.starts_with("RAFT__") || key == "CONFIG_PATH" { - std::env::remove_var(&key); + unsafe { + std::env::remove_var(&key); + } } } } diff --git a/d-engine-core/src/config/raft.rs b/d-engine-core/src/config/raft.rs index efe003fa..ace3c189 100644 --- a/d-engine-core/src/config/raft.rs +++ b/d-engine-core/src/config/raft.rs @@ -416,6 +416,9 @@ pub struct SnapshotConfig { #[serde(default = "default_snapshots_dir")] pub snapshots_dir: PathBuf, + #[serde(default = "default_snapshots_dir_prefix")] + pub snapshots_dir_prefix: String, + /// Size (in bytes) of individual chunks when transferring snapshots /// /// Default: `default_chunk_size()` (typically 1MB) @@ -468,6 +471,7 @@ impl Default for SnapshotConfig { snapshot_cool_down_since_last_check: default_snapshot_cool_down_since_last_check(), cleanup_retain_count: default_cleanup_retain_count(), snapshots_dir: default_snapshots_dir(), + snapshots_dir_prefix: default_snapshots_dir_prefix(), chunk_size: default_chunk_size(), retained_log_entries: default_retained_log_entries(), sender_yield_every_n_chunks: default_sender_yield_every_n_chunks(), @@ -573,6 +577,10 @@ fn default_cleanup_retain_count() -> u64 { fn default_snapshots_dir() -> PathBuf { PathBuf::from("/tmp/snapshots") } +/// Default snapshots directory prefix +fn default_snapshots_dir_prefix() -> String { + "snapshot-".to_string() +} /// 1KB chunks by default fn default_chunk_size() -> usize { diff --git a/d-engine-core/src/election/election_handler_test.rs b/d-engine-core/src/election/election_handler_test.rs index 6b4706b8..79876feb 100644 --- a/d-engine-core/src/election/election_handler_test.rs +++ b/d-engine-core/src/election/election_handler_test.rs @@ -183,7 +183,7 @@ async fn test_broadcast_vote_requests_case3() { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); diff --git a/d-engine-core/src/event.rs b/d-engine-core/src/event.rs index 832fcf14..83881d9a 100644 --- a/d-engine-core/src/event.rs +++ b/d-engine-core/src/event.rs @@ -37,7 +37,7 @@ pub(crate) struct NewCommitData { #[derive(Debug)] #[allow(dead_code)] -pub(crate) enum RoleEvent { +pub enum RoleEvent { BecomeFollower(Option), // BecomeFollower(Option) BecomeCandidate, BecomeLeader, @@ -49,7 +49,7 @@ pub(crate) enum RoleEvent { } #[derive(Debug)] -pub(crate) enum RaftEvent { +pub enum RaftEvent { ReceiveVoteRequest( VoteRequest, MaybeCloneOneshotSender>, @@ -123,7 +123,7 @@ pub(crate) enum RaftEvent { #[cfg(test)] #[cfg_attr(test, derive(Debug, Clone))] #[allow(unused)] -pub(crate) enum TestEvent { +pub enum TestEvent { ReceiveVoteRequest(VoteRequest), ClusterConf(MetadataRequest), @@ -159,7 +159,7 @@ pub(crate) enum TestEvent { } #[cfg(test)] -pub(crate) fn raft_event_to_test_event(event: &RaftEvent) -> TestEvent { +pub fn raft_event_to_test_event(event: &RaftEvent) -> TestEvent { match event { RaftEvent::ReceiveVoteRequest(req, _) => TestEvent::ReceiveVoteRequest(*req), RaftEvent::ClusterConf(req, _) => TestEvent::ClusterConf(*req), diff --git a/d-engine-core/src/lib.rs b/d-engine-core/src/lib.rs index 5c08ed55..1c606957 100644 --- a/d-engine-core/src/lib.rs +++ b/d-engine-core/src/lib.rs @@ -18,17 +18,17 @@ mod type_config; mod utils; pub use config::*; +pub use errors::*; +pub use event::*; pub use membership::*; pub use network::*; +pub use raft::*; pub use storage::*; pub(crate) use commit_handler::*; pub(crate) use election::*; -pub(crate) use errors::*; -pub(crate) use event::*; pub(crate) use maybe_clone_oneshot::*; pub(crate) use purge::*; -pub(crate) use raft::*; pub(crate) use raft_context::*; #[doc(hidden)] @@ -47,13 +47,8 @@ mod raft_oneshot_test; #[cfg(test)] mod raft_test; -use bytes::Bytes; -use d_engine_proto::common::EntryPayload; -use d_engine_proto::common::MembershipChange; -use d_engine_proto::common::Noop; -use d_engine_proto::common::entry_payload::Payload; -use d_engine_proto::common::membership_change::Change; -use tracing::instrument; +#[cfg(any(test, feature = "test-utils"))] +pub mod test_utils; /// In raft, during any Leader to Peer communication, /// if received response term is bigger than Leader's, @@ -84,7 +79,7 @@ pub(crate) fn if_higher_term_found( /// entries in the logs. If the logs have last entries with different terms, then the log with the /// later term is more up-to-date. If the logs end with the same term, then whichever log is longer /// is more up-to-date. -#[instrument] +#[tracing::instrument] pub(crate) fn is_target_log_more_recent( my_last_log_index: u64, my_last_log_term: u64, diff --git a/d-engine-core/src/maybe_clone_oneshot.rs b/d-engine-core/src/maybe_clone_oneshot.rs index a1570235..0320403b 100644 --- a/d-engine-core/src/maybe_clone_oneshot.rs +++ b/d-engine-core/src/maybe_clone_oneshot.rs @@ -22,7 +22,7 @@ use tokio::sync::broadcast; use tokio::sync::oneshot; use tonic::Status; -use crate::proto::storage::SnapshotChunk; +use d_engine_proto::server::storage::SnapshotChunk; pub trait RaftOneshot { type Sender: Send + Sync; @@ -85,7 +85,7 @@ impl MaybeCloneOneshotSender { impl MaybeCloneOneshotReceiver { #[cfg(test)] pub async fn recv(&mut self) -> Result { - if let Some(ref mut rx) = &mut self.test_inner { + if let Some(rx) = &mut self.test_inner { rx.recv().await } else { // Fallback for non-cloneable types @@ -116,7 +116,7 @@ impl Future for MaybeCloneOneshotReceiver { let this = self.get_mut(); // Using the recv method of tokio::sync::broadcast::Receiver - if let Some(ref mut rx) = &mut this.test_inner { + if let Some(rx) = &mut this.test_inner { match rx.try_recv() { Ok(value) => Poll::Ready(Ok(value)), Err(broadcast::error::TryRecvError::Empty) => { diff --git a/d-engine-core/src/membership.rs b/d-engine-core/src/membership.rs index 917a200b..f7e54f45 100644 --- a/d-engine-core/src/membership.rs +++ b/d-engine-core/src/membership.rs @@ -2,7 +2,9 @@ use mockall::automock; use tonic::async_trait; use tonic::transport::Channel; +use tracing::warn; +use crate::MembershipError; use crate::Result; use crate::TypeConfig; use d_engine_proto::common::MembershipChange; @@ -14,13 +16,13 @@ use d_engine_proto::server::cluster::NodeMeta; // Add connection type management in RpcPeerChannels #[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub(crate) enum ConnectionType { +pub enum ConnectionType { Control, // Used for key operations such as elections/heartbeats Data, // Used for log replication Bulk, // Used for high-traffic operations such as snapshot transmission } impl ConnectionType { - pub(crate) fn all() -> Vec { + pub fn all() -> Vec { vec![ ConnectionType::Control, ConnectionType::Data, @@ -191,3 +193,32 @@ where role: i32, ) -> Result<()>; } + +pub fn ensure_safe_join( + node_id: u32, + current_voters: usize, +) -> Result<()> { + // Total voters including leader = current_voters + 1 + let total_voters = current_voters + 1; + + // Always allow if cluster will have even number of voters + if (total_voters + 1).is_multiple_of(2) { + Ok(()) + } else { + // metrics::counter!("cluster.unsafe_join_attempts", 1); + metrics::counter!( + "cluster.unsafe_join_attempts", + &[("node_id", node_id.to_string())] + ) + .increment(1); + + warn!( + "Unsafe join attempt: current_voters={} (total_voters={})", + current_voters, total_voters + ); + Err( + MembershipError::JoinClusterError("Cluster must maintain odd number of voters".into()) + .into(), + ) + } +} diff --git a/src/network/grpc/backgroup_snapshot_transfer.rs b/d-engine-core/src/network/backgroup_snapshot_transfer.rs similarity index 98% rename from src/network/grpc/backgroup_snapshot_transfer.rs rename to d-engine-core/src/network/backgroup_snapshot_transfer.rs index d1ca9c0b..89d4fa80 100644 --- a/src/network/grpc/backgroup_snapshot_transfer.rs +++ b/d-engine-core/src/network/backgroup_snapshot_transfer.rs @@ -6,31 +6,31 @@ use std::pin::Pin; use std::sync::Arc; use std::time::Duration; -use futures::stream::BoxStream; use futures::Stream; use futures::StreamExt; +use futures::stream::BoxStream; use lru::LruCache; use tokio::sync::mpsc; use tokio::sync::mpsc::error::TrySendError; -use tokio::time::sleep; use tokio::time::Instant; +use tokio::time::sleep; use tokio_stream::wrappers::ReceiverStream; +use tonic::Status; use tonic::codec::CompressionEncoding; use tonic::transport::Channel; -use tonic::Status; use tracing::debug; use tracing::trace; use tracing::warn; -use crate::proto::storage::snapshot_ack::ChunkStatus; -use crate::proto::storage::snapshot_service_client::SnapshotServiceClient; -use crate::proto::storage::SnapshotAck; -use crate::proto::storage::SnapshotChunk; use crate::NetworkError; use crate::Result; use crate::SnapshotConfig; use crate::SnapshotError; use crate::TypeConfig; +use d_engine_proto::server::storage::SnapshotAck; +use d_engine_proto::server::storage::SnapshotChunk; +use d_engine_proto::server::storage::snapshot_ack::ChunkStatus; +use d_engine_proto::server::storage::snapshot_service_client::SnapshotServiceClient; pub(crate) struct BackgroundSnapshotTransfer { _marker: PhantomData, diff --git a/src/network/grpc/backgroup_snapshot_transfer_test.rs b/d-engine-core/src/network/backgroup_snapshot_transfer_test.rs similarity index 99% rename from src/network/grpc/backgroup_snapshot_transfer_test.rs rename to d-engine-core/src/network/backgroup_snapshot_transfer_test.rs index daf57b6b..d392e190 100644 --- a/src/network/grpc/backgroup_snapshot_transfer_test.rs +++ b/d-engine-core/src/network/backgroup_snapshot_transfer_test.rs @@ -1,24 +1,24 @@ use std::time::Duration; use bytes::Bytes; -use futures::stream; use futures::StreamExt; +use futures::stream; use tokio::sync::oneshot; use tokio::time::Instant; use tonic::Status; use tracing::debug; use super::*; -use crate::proto::storage::SnapshotChunk; -use crate::proto::storage::SnapshotResponse; -use crate::test_utils::create_snapshot_stream; -use crate::test_utils::MockNode; -use crate::test_utils::MockTypeConfig; use crate::Error; use crate::NetworkError; use crate::SnapshotConfig; use crate::SnapshotError; use crate::StorageError; +use crate::test_utils::MockNode; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::create_snapshot_stream; +use d_engine_proto::server::storage::SnapshotChunk; +use d_engine_proto::server::storage::SnapshotResponse; fn default_snapshot_config() -> SnapshotConfig { SnapshotConfig { diff --git a/d-engine-core/src/network/mod.rs b/d-engine-core/src/network/mod.rs index 8e6c2e89..94f67916 100644 --- a/d-engine-core/src/network/mod.rs +++ b/d-engine-core/src/network/mod.rs @@ -6,8 +6,11 @@ //! are governed by timeout parameters defined in [`RaftConfig`] to ensure //! system responsiveness. -// #[cfg(test)] -// mod network_test; +mod backgroup_snapshot_transfer; + +#[cfg(test)] +mod backgroup_snapshot_transfer_test; +pub(crate) use backgroup_snapshot_transfer::*; #[cfg(test)] use mockall::automock; diff --git a/d-engine-core/src/raft.rs b/d-engine-core/src/raft.rs index 42e29aea..4480678f 100644 --- a/d-engine-core/src/raft.rs +++ b/d-engine-core/src/raft.rs @@ -32,12 +32,12 @@ pub struct Raft where T: TypeConfig, { - pub(crate) node_id: u32, + pub node_id: u32, pub(crate) role: RaftRole, - pub(crate) ctx: RaftContext, + pub ctx: RaftContext, // Network & Storage events - pub(crate) event_tx: mpsc::Sender, + pub event_tx: mpsc::Sender, event_rx: mpsc::Receiver, // Timer diff --git a/d-engine-core/src/raft_role/follower_state_test.rs b/d-engine-core/src/raft_role/follower_state_test.rs index ed518039..ff771084 100644 --- a/d-engine-core/src/raft_role/follower_state_test.rs +++ b/d-engine-core/src/raft_role/follower_state_test.rs @@ -20,20 +20,17 @@ use crate::MockElectionCore; use crate::MockMembership; use crate::MockPurgeExecutor; use crate::MockReplicationCore; -use crate::MockStateMachine; use crate::MockStateMachineHandler; use crate::NetworkError; use crate::NewCommitData; use crate::RaftEvent; use crate::RaftOneshot; -use crate::RaftTypeConfig; use crate::RoleEvent; use crate::SnapshotError; use crate::StateUpdate; use crate::SystemError; use crate::role_state::RaftRoleState; use crate::test_utils::MockBuilder; -use crate::test_utils::MockStorageEngine; use crate::test_utils::MockTypeConfig; use crate::test_utils::mock_raft_context; use crate::test_utils::node_config; @@ -147,7 +144,7 @@ async fn test_new_with_restart() { }), }); let last_applied_index_option = Some(2); - let state = FollowerState::>::new( + let state = FollowerState::::new( node_id, node_config, hard_state_from_db, @@ -1449,7 +1446,7 @@ async fn test_handle_raft_event_case10() { // Step 2: Prepare the event let request = JoinRequest { node_id: 2, - node_role: Learner, + node_role: Learner.into(), address: "127.0.0.1:9090".to_string(), }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); diff --git a/d-engine-core/src/raft_role/leader_state.rs b/d-engine-core/src/raft_role/leader_state.rs index 7d0cf48c..a0140a46 100644 --- a/d-engine-core/src/raft_role/leader_state.rs +++ b/d-engine-core/src/raft_role/leader_state.rs @@ -30,6 +30,7 @@ use super::StateSnapshot; use super::candidate_state::CandidateState; use super::role_state::RaftRoleState; use crate::AppendResults; +use crate::BackgroundSnapshotTransfer; use crate::BatchBuffer; use crate::ConnectionType; use crate::ConsensusError; @@ -67,7 +68,6 @@ use crate::alias::SMHOF; use crate::client_command_to_entry_payloads; use crate::cluster::is_majority; use crate::ensure_safe_join; -use crate::grpc::BackgroundSnapshotTransfer; use crate::scoped_timer::ScopedTimer; use crate::stream::create_production_snapshot_stream; use crate::utils::cluster::error; @@ -1202,7 +1202,7 @@ impl LeaderState { current_term: self.current_term(), voted_for: None, commit_index: self.commit_index(), - role: Leader, + role: Leader.into(), } } @@ -1339,7 +1339,7 @@ impl LeaderState { new_commit_index ); self.update_commit_index_with_signal( - Leader, + Leader.into(), self.current_term(), new_commit_index, role_tx, diff --git a/d-engine-core/src/raft_role/leader_state_test.rs b/d-engine-core/src/raft_role/leader_state_test.rs index 2e69ed58..1048a6ad 100644 --- a/d-engine-core/src/raft_role/leader_state_test.rs +++ b/d-engine-core/src/raft_role/leader_state_test.rs @@ -37,14 +37,12 @@ use crate::RaftEvent; use crate::RaftNodeConfig; use crate::RaftOneshot; use crate::RaftRequestWithSignal; -use crate::RaftTypeConfig; use crate::ReplicationError; use crate::RoleEvent; use crate::SnapshotError; use crate::client_command_to_entry_payloads; use crate::convert::safe_kv_bytes; use crate::test_utils::MockBuilder; -use crate::test_utils::MockStorageEngine; use crate::test_utils::MockTypeConfig; use crate::test_utils::crate_test_snapshot_stream; use crate::test_utils::create_test_chunk; @@ -1163,7 +1161,7 @@ mod snapshot_created_event_tests { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -1215,7 +1213,7 @@ mod snapshot_created_event_tests { vec![NodeMeta { id: 2, address: "".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -1357,7 +1355,7 @@ mod snapshot_created_event_tests { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -1431,7 +1429,7 @@ mod snapshot_created_event_tests { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -1503,13 +1501,13 @@ mod snapshot_created_event_tests { NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 3, address: "http://127.0.0.1:55002".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, ] @@ -1655,7 +1653,7 @@ async fn test_handle_raft_event_case10_1_discover_leader_success() { Some(NodeMeta { id: 1, address: "127.0.0.1:50051".to_string(), - role: Leader, + role: Leader.into(), status: NodeStatus::Active.into(), }) }); @@ -1735,7 +1733,7 @@ async fn test_handle_raft_event_case10_4_different_leader_terms() { Some(NodeMeta { id: 1, address: "127.0.0.1:50051".to_string(), - role: Leader, + role: Leader.into(), status: NodeStatus::Active.into(), }) }); @@ -1781,7 +1779,7 @@ async fn test_handle_raft_event_case10_5_invalid_node_id() { Some(NodeMeta { id: 1, address: "127.0.0.1:50051".to_string(), - role: Leader, + role: Leader.into(), status: NodeStatus::Active.into(), }) }); @@ -1812,9 +1810,9 @@ async fn test_handle_raft_event_case10_5_invalid_node_id() { fn test_state_size() { println!( "LeaderState size: {}", - size_of::>>() + size_of::>() ); - assert!(size_of::>>() <= 360); + assert!(size_of::>() <= 360); } /// # Case 1: Valid purge conditions with cluster consensus @@ -2139,13 +2137,13 @@ async fn test_process_batch_case2_2_quorum_non_verifiable_failure() { vec![ NodeMeta { id: 2, - role: Follower, + role: Follower.into(), address: "127.0.0.1:0".to_string(), status: NodeStatus::Active.into(), }, NodeMeta { id: 3, - role: Follower, + role: Follower.into(), address: "127.0.0.1:0".to_string(), status: NodeStatus::Active.into(), }, @@ -2253,13 +2251,13 @@ async fn test_process_batch_case4_partial_timeouts() { NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 3, address: "http://127.0.0.1:55002".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, ] @@ -2313,13 +2311,13 @@ async fn test_process_batch_case5_all_timeout() { NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 3, address: "http://127.0.0.1:55002".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, ] @@ -2522,19 +2520,19 @@ async fn test_verify_internal_quorum_case3_non_verifiable_failure() { NodeMeta { id: 2, address: "".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 3, address: "".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 4, address: "".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, ] @@ -2725,7 +2723,7 @@ async fn test_handle_join_cluster_case1_success() { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -2788,7 +2786,7 @@ async fn test_handle_join_cluster_case1_success() { .handle_join_cluster( JoinRequest { node_id, - node_role: Learner, + node_role: Learner.into(), address: address.clone(), }, sender, @@ -2830,7 +2828,7 @@ async fn test_handle_join_cluster_case2_node_exists() { .handle_join_cluster( JoinRequest { node_id, - node_role: Learner, + node_role: Learner.into(), address: "127.0.0.1:8080".to_string(), }, sender, @@ -2886,7 +2884,7 @@ async fn test_handle_join_cluster_case3_quorum_failed() { .handle_join_cluster( JoinRequest { node_id, - node_role: Learner, + node_role: Learner.into(), address: "127.0.0.1:8080".to_string(), }, sender, @@ -2923,7 +2921,7 @@ async fn test_handle_join_cluster_case4_quorum_error() { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -2943,7 +2941,7 @@ async fn test_handle_join_cluster_case4_quorum_error() { .handle_join_cluster( JoinRequest { node_id, - node_role: Learner, + node_role: Learner.into(), address: "127.0.0.1:8080".to_string(), }, sender, @@ -2986,7 +2984,7 @@ async fn test_handle_join_cluster_case5_snapshot_triggered() { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -3043,7 +3041,7 @@ async fn test_handle_join_cluster_case5_snapshot_triggered() { .handle_join_cluster( JoinRequest { node_id, - node_role: Learner, + node_role: Learner.into(), address, }, sender, @@ -3065,7 +3063,7 @@ mod trigger_background_snapshot_test { use super::*; use crate::SnapshotConfig; - use crate::core::raft_role::leader_state::LeaderState; + use crate::raft_role::leader_state::LeaderState; use d_engine_proto::server::storage::SnapshotChunk; use d_engine_proto::server::storage::SnapshotMetadata; @@ -3170,9 +3168,9 @@ mod batch_promote_learners_test { use super::*; use crate::RaftContext; - use crate::core::raft_role::RoleEvent; - use crate::core::raft_role::leader_state::LeaderState; use crate::membership::MockMembership; + use crate::raft_role::RoleEvent; + use crate::raft_role::leader_state::LeaderState; use d_engine_proto::common::NodeStatus; enum VerifyInternalQuorumWithRetrySuccess { @@ -3210,7 +3208,7 @@ mod batch_promote_learners_test { .map(|id| NodeMeta { id: id as u32, address: format!("addr_{id}"), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }) .collect() @@ -3411,7 +3409,7 @@ mod pending_promotion_tests { id, address: "".to_string(), status: NodeStatus::Active as i32, - role: Follower, + role: Follower.into(), }) .collect() }); @@ -3603,7 +3601,7 @@ mod pending_promotion_tests { id, address: "".to_string(), status: NodeStatus::Active as i32, - role: Follower, + role: Follower.into(), }) .collect() }); diff --git a/d-engine-core/src/raft_role/learner_state.rs b/d-engine-core/src/raft_role/learner_state.rs index 4c3d895c..2d769dff 100644 --- a/d-engine-core/src/raft_role/learner_state.rs +++ b/d-engine-core/src/raft_role/learner_state.rs @@ -472,7 +472,7 @@ impl RaftRoleState for LearnerState { leader_id, JoinRequest { node_id: node_config.cluster.node_id, - node_role: Learner, + node_role: Learner.into(), address: node_config.cluster.listen_address.to_string(), }, node_config.retry.join_cluster, @@ -537,7 +537,7 @@ impl LearnerState { current_term: self.current_term(), voted_for: None, commit_index: self.commit_index(), - role: Learner, + role: Learner.into(), } } } diff --git a/d-engine-core/src/raft_role/learner_state_test.rs b/d-engine-core/src/raft_role/learner_state_test.rs index beb88fcc..a109894e 100644 --- a/d-engine-core/src/raft_role/learner_state_test.rs +++ b/d-engine-core/src/raft_role/learner_state_test.rs @@ -516,7 +516,7 @@ async fn test_handle_raft_event_case10() { // Step 2: Prepare the event let request = JoinRequest { node_id: 2, - node_role: Learner, + node_role: Learner.into(), address: "127.0.0.1:9090".to_string(), }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); diff --git a/d-engine-core/src/raft_test.rs b/d-engine-core/src/raft_test.rs index 7f18ce1b..bfc40682 100644 --- a/d-engine-core/src/raft_test.rs +++ b/d-engine-core/src/raft_test.rs @@ -29,6 +29,9 @@ use crate::cluster::is_learner; use crate::leader_state::LeaderState; use crate::test_utils::MockTypeConfig; use crate::test_utils::mock_raft; +use d_engine_proto::common::NodeRole::Candidate; +use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeRole::Leader; use d_engine_proto::common::NodeStatus; use d_engine_proto::server::cluster::MetadataRequest; use d_engine_proto::server::cluster::NodeMeta; @@ -396,7 +399,7 @@ async fn test_election_timeout_case4() { NodeMeta { id: peer1_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { @@ -730,7 +733,7 @@ async fn test_handle_role_event_state_update_case1_1() { raft.register_new_commit_listener(tx); raft.handle_role_event(RoleEvent::NotifyNewCommitIndex(NewCommitData { new_commit_index, - role: Leader, + role: Leader.into(), current_term: 1, })) .await @@ -760,7 +763,7 @@ async fn test_handle_role_event_state_update_case1_2() { raft.register_new_commit_listener(tx); raft.handle_role_event(RoleEvent::NotifyNewCommitIndex(NewCommitData { new_commit_index, - role: Leader, + role: Leader.into(), current_term: 1, })) .await @@ -800,7 +803,7 @@ async fn test_handle_role_event_state_update_case1_3_1() { raft.register_new_commit_listener(tx); raft.handle_role_event(RoleEvent::NotifyNewCommitIndex(NewCommitData { new_commit_index, - role: Leader, + role: Leader.into(), current_term: 1, })) .await @@ -831,7 +834,7 @@ async fn test_handle_role_event_state_update_case1_3_2() { vec![NodeMeta { id: 2, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -912,7 +915,7 @@ async fn test_handle_role_event_state_update_case1_4() { raft.register_new_commit_listener(tx); raft.handle_role_event(RoleEvent::NotifyNewCommitIndex(NewCommitData { new_commit_index, - role: Leader, + role: Leader.into(), current_term: 1, })) .await diff --git a/d-engine-core/src/replication/mod.rs b/d-engine-core/src/replication/mod.rs index 64bc781a..02a97e42 100644 --- a/d-engine-core/src/replication/mod.rs +++ b/d-engine-core/src/replication/mod.rs @@ -36,12 +36,10 @@ use crate::alias::ROF; use d_engine_proto::client::ClientResponse; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; -use d_engine_proto::common::LogId; use d_engine_proto::server::replication::AppendEntriesRequest; use d_engine_proto::server::replication::AppendEntriesResponse; use d_engine_proto::server::replication::ConflictResult; use d_engine_proto::server::replication::SuccessResult; -use d_engine_proto::server::replication::append_entries_response; /// Request with response channel that can handle all Raft payload types #[derive(Debug)] diff --git a/d-engine-core/src/replication/replication_handler.rs b/d-engine-core/src/replication/replication_handler.rs index 5c38b029..931a4d3d 100644 --- a/d-engine-core/src/replication/replication_handler.rs +++ b/d-engine-core/src/replication/replication_handler.rs @@ -25,11 +25,11 @@ use crate::RaftContext; use crate::RaftLog; use crate::ReplicationError; use crate::Result; -use crate::ScopedTimer; use crate::StateSnapshot; use crate::Transport; use crate::TypeConfig; use crate::alias::ROF; +use crate::scoped_timer::ScopedTimer; use crate::utils::cluster::is_majority; use d_engine_proto::client::WriteCommand; use d_engine_proto::common::Entry; diff --git a/d-engine-core/src/replication/replication_handler_test.rs b/d-engine-core/src/replication/replication_handler_test.rs index f7ac00b3..db65afc5 100644 --- a/d-engine-core/src/replication/replication_handler_test.rs +++ b/d-engine-core/src/replication/replication_handler_test.rs @@ -15,8 +15,6 @@ use super::ReplicationHandler; use crate::AppendResult; use crate::ConsensusError; use crate::Error; -use crate::FileStorageEngine; -use crate::Leader; use crate::LeaderStateSnapshot; use crate::MockMembership; use crate::MockRaftLog; @@ -25,7 +23,6 @@ use crate::MockTransport; use crate::NetworkError; use crate::PeerUpdate; use crate::RaftLog; -use crate::RaftTypeConfig; use crate::ReplicationError; use crate::StateSnapshot; use crate::StorageError; @@ -44,6 +41,7 @@ use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; use d_engine_proto::common::LogId; use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeRole::Leader; use d_engine_proto::common::NodeRole::Learner; use d_engine_proto::common::NodeStatus; use d_engine_proto::common::entry_payload::Payload; @@ -888,7 +886,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 1, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::new(), @@ -928,7 +926,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 1, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::new(), @@ -941,7 +939,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -949,7 +947,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -1008,7 +1006,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 1, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::new(), @@ -1073,7 +1071,7 @@ mod handle_raft_request_in_batch_test { current_term: 2, // Leader's term is 2 voted_for: None, commit_index: 1, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from_iter(vec![(peer2_id, 3)]), @@ -1106,7 +1104,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -1142,7 +1140,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 1, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::new(), @@ -1174,7 +1172,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -1229,7 +1227,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(peer2_id, 6)]), @@ -1265,7 +1263,7 @@ mod handle_raft_request_in_batch_test { membership.expect_replication_peers().returning(move || { vec![NodeMeta { id: peer2_id, - role: Follower, + role: Follower.into(), address: "".to_string(), status: NodeStatus::Active.into(), }] @@ -1338,7 +1336,7 @@ mod handle_raft_request_in_batch_test { current_term: 6, voted_for: None, commit_index: 10, - role: Leader, + role: Leader.into(), }; // Initial next_index values from test case description @@ -1426,7 +1424,7 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: id as u32, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), } }) @@ -1568,7 +1566,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(peer2_id, 6), (peer3_id, 6), (peer4_id, 6), (peer5_id, 6)]), @@ -1622,25 +1620,25 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: peer3_id, address: "http://127.0.0.1:55002".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: peer4_id, address: "http://127.0.0.1:55003".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: peer5_id, address: "http://127.0.0.1:55004".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, ] @@ -1731,7 +1729,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(peer2_id, 6), (peer3_id, 6)]), @@ -1775,13 +1773,13 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: peer3_id, address: "http://127.0.0.1:55002".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, ] @@ -1860,7 +1858,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(peer2_id, 6), (peer3_id, 6)]), @@ -1905,13 +1903,13 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: peer3_id, address: "http://127.0.0.1:55002".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, ] @@ -1981,7 +1979,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(peer2_id, 6), (peer3_id, 6)]), @@ -2026,13 +2024,13 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: peer3_id, address: "http://127.0.0.1:55002".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, ] @@ -2097,7 +2095,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(peer2_id, 6), (peer3_id, 6)]), @@ -2200,7 +2198,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(peer2_id, 6)]), @@ -2232,7 +2230,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: peer2_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -2283,13 +2281,13 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: voter_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: learner_id, address: "http://127.0.0.1:55002".to_string(), - role: Learner, + role: Learner.into(), status: NodeStatus::Joining.into(), }, ] @@ -2298,7 +2296,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: voter_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -2339,7 +2337,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(voter_id, 6), (learner_id, 6)]), @@ -2388,13 +2386,13 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: voter_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: pending_id, address: "http://127.0.0.1:55002".to_string(), - role: Learner, + role: Learner.into(), status: NodeStatus::Syncing.into(), }, ] @@ -2403,7 +2401,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: voter_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), }] }); @@ -2444,7 +2442,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(voter_id, 6), (pending_id, 6)]), @@ -2492,13 +2490,13 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: learner1, address: "http://127.0.0.1:55001".to_string(), - role: Learner, + role: Learner.into(), status: NodeStatus::Joining.into(), }, NodeMeta { id: learner2, address: "http://127.0.0.1:55002".to_string(), - role: Learner, + role: Learner.into(), status: NodeStatus::Joining.into(), }, ] @@ -2541,7 +2539,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(learner1, 6), (learner2, 6)]), @@ -2603,7 +2601,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 1, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::new(), @@ -2646,19 +2644,19 @@ mod handle_raft_request_in_batch_test { NodeMeta { id: voter_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active as i32, }, NodeMeta { id: joining_id, address: "http://127.0.0.1:55002".to_string(), - role: Learner, + role: Learner.into(), status: NodeStatus::Joining as i32, }, NodeMeta { id: pending_id, address: "http://127.0.0.1:55003".to_string(), - role: Learner, + role: Learner.into(), status: NodeStatus::Syncing as i32, }, ] @@ -2667,7 +2665,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: voter_id, address: "http://127.0.0.1:55001".to_string(), - role: Follower, + role: Follower.into(), status: NodeStatus::Active as i32, }] }); @@ -2713,7 +2711,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 10, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(voter_id, 11), (joining_id, 11), (pending_id, 11)]), @@ -2759,14 +2757,14 @@ mod handle_raft_request_in_batch_test { vec![ NodeMeta { id: voter_id, - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), ..Default::default() }, NodeMeta { id: pending_id, status: NodeStatus::Joining as i32, - role: Learner, + role: Learner.into(), ..Default::default() }, ] @@ -2774,7 +2772,7 @@ mod handle_raft_request_in_batch_test { membership.expect_voters().returning(move || { vec![NodeMeta { id: voter_id, - role: Follower, + role: Follower.into(), status: NodeStatus::Active.into(), ..Default::default() }] @@ -2816,7 +2814,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 5, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(2, 6), (3, 6)]), @@ -2905,7 +2903,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: leader_commit_index, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(learner_id, learner_match_index + 1)]), @@ -2949,7 +2947,7 @@ mod handle_raft_request_in_batch_test { vec![NodeMeta { id: learner_id, address: "http://127.0.0.1:55001".to_string(), - role: Learner, + role: Learner.into(), status: NodeStatus::Syncing.into(), }] }); @@ -2989,7 +2987,7 @@ mod handle_raft_request_in_batch_test { current_term: 1, voted_for: None, commit_index: 1, - role: Leader, + role: Leader.into(), }; let leader_state_snapshot = LeaderStateSnapshot { next_index: HashMap::from([(learner_id, 1 + 1)]), diff --git a/d-engine-core/src/state_machine_handler/default_state_machine_handler.rs b/d-engine-core/src/state_machine_handler/default_state_machine_handler.rs index c66b937f..68c69e7b 100644 --- a/d-engine-core/src/state_machine_handler/default_state_machine_handler.rs +++ b/d-engine-core/src/state_machine_handler/default_state_machine_handler.rs @@ -53,7 +53,6 @@ use crate::TypeConfig; use crate::alias::ROF; use crate::alias::SMOF; use crate::alias::SNP; -use crate::constants::SNAPSHOT_DIR_PREFIX; use crate::convert::classify_error; use crate::file_io::validate_checksum; use crate::file_io::validate_compressed_format; @@ -358,6 +357,7 @@ where .cleanup_snapshot( self.snapshot_config.cleanup_retain_count, &self.snapshot_config.snapshots_dir, + &self.snapshot_config.snapshots_dir_prefix, ) .await { @@ -380,6 +380,7 @@ where &self, retain_count: u64, snapshot_dir: &Path, + snapshot_dir_prefix: &str, ) -> Result<()> { // Phase 1: Collect and parse snapshots let mut snapshots = Vec::new(); @@ -396,9 +397,12 @@ where if path.extension().is_some_and(|ext| ext == "gz") { if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) { - let parsed = parse_snapshot_dirname(file_name).or_else(|| { - file_name.strip_suffix(".tar.gz").and_then(parse_snapshot_dirname) - }); + let parsed = + parse_snapshot_dirname(file_name, snapshot_dir_prefix).or_else(|| { + file_name + .strip_suffix(".tar.gz") + .and_then(|s| parse_snapshot_dirname(s, snapshot_dir_prefix)) + }); let (index, term) = if let Some(pair) = parsed { pair @@ -698,7 +702,7 @@ where snapshot_policy, path_mgr: Arc::new(SnapshotPathManager::new( snapshot_config.snapshots_dir.clone(), - SNAPSHOT_DIR_PREFIX.to_string(), + snapshot_config.snapshots_dir_prefix.clone(), )), snapshot_config, @@ -1108,11 +1112,14 @@ fn zero_copy_bytes_from_mmap( } /// Manual parsing file name format: snapshot-{index}-{term} -fn parse_snapshot_dirname(name: &str) -> Option<(u64, u64)> { +fn parse_snapshot_dirname( + name: &str, + snapshot_dir_prefix: &str, +) -> Option<(u64, u64)> { debug!(%name, "parse_snapshot_dirname"); // Check prefix and suffix - if !name.starts_with(SNAPSHOT_DIR_PREFIX) { + if !name.starts_with(snapshot_dir_prefix) { return None; } diff --git a/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs index 1695e748..567d5e41 100644 --- a/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs +++ b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs @@ -34,11 +34,9 @@ use crate::MockRaftLog; use crate::MockReplicationCore; use crate::MockSnapshotPolicy; use crate::MockStateMachine; -use crate::Node; use crate::SnapshotError; use crate::StateUpdate; use crate::StorageError; -use crate::constants::SNAPSHOT_DIR_PREFIX; use crate::test_utils::MOCK_STATE_MACHINE_HANDLER_PORT_BASE; use crate::test_utils::MockBuilder; use crate::test_utils::MockTypeConfig; @@ -330,71 +328,71 @@ fn listen_addr(port: u32) -> SocketAddr { format!("127.0.0.1:{port}",).parse().unwrap() } -/// Case1: Complete successful snapshot installation -#[tokio::test] -#[traced_test] -async fn test_install_snapshot_case1() { - let port = MOCK_STATE_MACHINE_HANDLER_PORT_BASE + 1; - // 1. Simulate node with RPC server running in a new thread - let (graceful_tx, graceful_rx) = watch::channel(()); - let node = mock_node_with_rpc_service( - "/tmp/test_install_snapshot_case1", - listen_addr(port), - false, - graceful_rx, - None, - ); - node.set_ready(true); - - // 3. Start the Raft main loop - let raft_handle = tokio::spawn(async move { - let mut raft = node.raft_core.lock().await; - let _ = time::timeout(Duration::from_millis(100), raft.run()).await; - }); - - // 2. Crate RPC client - let addr: SocketAddr = format!("[::]:{port}",).parse().unwrap(); - let mut rpc_client = SnapshotServiceClient::connect(format!( - "grpc://localhost:{}", - addr.to_string().split(':').next_back().unwrap() - )) - .await - .unwrap(); - - // 3. Fake install snapshot request stream - let total_chunks = 3; - let (tx, rx) = tokio::sync::mpsc::channel(10); - // Generate valid chunks with seq 0..2 - let h = tokio::spawn(async move { - for seq in 0..total_chunks { - let chunk = create_test_chunk( - seq, - &format!("chunk-{seq}",).into_bytes(), - 3, // chunk term (higher than handler's current_term) - 1, // leader_id - total_chunks, - ); - - tx.send(chunk).await.expect("send failed"); - } - }); - // Convert mpsc receiver into tonic::Streaming - let request_stream = tokio_stream::wrappers::ReceiverStream::new(rx); - - // 4. Waiting to receive response - tokio::time::sleep(Duration::from_millis(20)).await; - - let response = rpc_client.install_snapshot(request_stream).await.unwrap().into_inner(); - - assert!(response.success); - assert_eq!(response.term, 1); // Should reflect handler's current_term - assert_eq!(response.next_chunk, 0); // Indicates full success - - // Release handler - graceful_tx.send(()).expect("shutdown successfully!"); - raft_handle.await.expect("should succeed"); - h.await.unwrap(); -} +// /// Case1: Complete successful snapshot installation +// #[tokio::test] +// #[traced_test] +// async fn test_install_snapshot_case1() { +// let port = MOCK_STATE_MACHINE_HANDLER_PORT_BASE + 1; +// // 1. Simulate node with RPC server running in a new thread +// let (graceful_tx, graceful_rx) = watch::channel(()); +// let node = mock_node_with_rpc_service( +// "/tmp/test_install_snapshot_case1", +// listen_addr(port), +// false, +// graceful_rx, +// None, +// ); +// node.set_ready(true); + +// // 3. Start the Raft main loop +// let raft_handle = tokio::spawn(async move { +// let mut raft = node.raft_core.lock().await; +// let _ = time::timeout(Duration::from_millis(100), raft.run()).await; +// }); + +// // 2. Crate RPC client +// let addr: SocketAddr = format!("[::]:{port}",).parse().unwrap(); +// let mut rpc_client = SnapshotServiceClient::connect(format!( +// "grpc://localhost:{}", +// addr.to_string().split(':').next_back().unwrap() +// )) +// .await +// .unwrap(); + +// // 3. Fake install snapshot request stream +// let total_chunks = 3; +// let (tx, rx) = tokio::sync::mpsc::channel(10); +// // Generate valid chunks with seq 0..2 +// let h = tokio::spawn(async move { +// for seq in 0..total_chunks { +// let chunk = create_test_chunk( +// seq, +// &format!("chunk-{seq}",).into_bytes(), +// 3, // chunk term (higher than handler's current_term) +// 1, // leader_id +// total_chunks, +// ); + +// tx.send(chunk).await.expect("send failed"); +// } +// }); +// // Convert mpsc receiver into tonic::Streaming +// let request_stream = tokio_stream::wrappers::ReceiverStream::new(rx); + +// // 4. Waiting to receive response +// tokio::time::sleep(Duration::from_millis(20)).await; + +// let response = rpc_client.install_snapshot(request_stream).await.unwrap().into_inner(); + +// assert!(response.success); +// assert_eq!(response.term, 1); // Should reflect handler's current_term +// assert_eq!(response.next_chunk, 0); // Indicates full success + +// // Release handler +// graceful_tx.send(()).expect("shutdown successfully!"); +// raft_handle.await.expect("should succeed"); +// h.await.unwrap(); +// } // Helper to create test handler fn create_test_handler( @@ -727,8 +725,8 @@ async fn test_apply_snapshot_stream_from_leader_case7() { } mod create_snapshot_tests { use super::*; - use crate::Leader; use crate::NewCommitData; + use d_engine_proto::common::NodeRole::Leader; /// # Case 1: Basic creation flow #[tokio::test] async fn test_create_snapshot_case1() { @@ -765,7 +763,7 @@ mod create_snapshot_tests { 0, 1, Arc::new(sm), - config, + config.clone(), MockSnapshotPolicy::new(), ); @@ -785,7 +783,7 @@ mod create_snapshot_tests { final_path .to_str() .unwrap() - .contains(&format!("{SNAPSHOT_DIR_PREFIX}5-1.tar.gz",)) + .contains(&format!("{}5-1.tar.gz", &config.snapshots_dir_prefix)) ); assert_eq!(metadata.last_included, Some(LogId { term: 1, index: 5 })); @@ -840,7 +838,7 @@ mod create_snapshot_tests { 0, 1, Arc::new(sm), - config, + config.clone(), snapshot_policy, )); tx.send(()).unwrap(); // Unblock the first task @@ -870,11 +868,11 @@ mod create_snapshot_tests { success_count, 1, "Expected exactly one successful snapshot creation" ); - assert_eq!(count_snapshots(&temp_path), 1); + assert_eq!(count_snapshots(&temp_path, &config.snapshots_dir_prefix), 1); // Verify flag is reset regardless of task outcome let ctx = NewCommitData { - role: Leader, + role: Leader.into(), current_term: 1, new_commit_index: 100, }; @@ -914,7 +912,7 @@ mod create_snapshot_tests { 3, // Current version 1, Arc::new(sm), - config, + config.clone(), MockSnapshotPolicy::new(), ); @@ -931,7 +929,8 @@ mod create_snapshot_tests { // Verify files are compressed for version in &[3, 4] { - let path = snapshot_dir.join(format!("{SNAPSHOT_DIR_PREFIX}{version}-1.tar.gz",)); + let path = + snapshot_dir.join(format!("{}{version}-1.tar.gz", config.snapshots_dir_prefix)); assert!(path.is_file(), "Snapshot file not found: {path:?}",); } } @@ -957,7 +956,7 @@ mod create_snapshot_tests { 0, 1, Arc::new(sm), - config, + config.clone(), MockSnapshotPolicy::new(), ); @@ -966,7 +965,7 @@ mod create_snapshot_tests { assert!(result.is_err()); // Verify no files created - assert_eq!(count_snapshots(&temp_path), 0); + assert_eq!(count_snapshots(&temp_path, &config.snapshots_dir_prefix), 0); } /// # Case 5: Test snapshot_in_progress flag is reset on success @@ -1039,7 +1038,10 @@ mod create_snapshot_tests { } // Helper functions -fn count_snapshots(dir: &Path) -> usize { +fn count_snapshots( + dir: &Path, + snapshots_dir_prefix: &str, +) -> usize { debug!(?dir, "count_snapshots"); // If the directory does not exist or cannot be accessed, just return 0 @@ -1055,7 +1057,7 @@ fn count_snapshots(dir: &Path) -> usize { entry .file_name() .to_str() - .and_then(|name| name.starts_with(SNAPSHOT_DIR_PREFIX).then_some(())) + .and_then(|name| name.starts_with(snapshots_dir_prefix).then_some(())) }) .count() } @@ -1091,18 +1093,22 @@ async fn test_cleanup_snapshot_case1() { let temp_dir = TempDir::new().unwrap(); let sm = MockStateMachine::new(); - create_test_files(&temp_dir, &[1, 2, 3]).await; + let config = snapshot_config(temp_dir.path().to_path_buf()); + create_test_files(&temp_dir, &[1, 2, 3], &config.snapshots_dir_prefix).await; let handler = DefaultStateMachineHandler::::new( 1, 0, 1, Arc::new(sm), - snapshot_config(temp_dir.path().to_path_buf()), + config.clone(), MockSnapshotPolicy::new(), ); - handler.cleanup_snapshot(2, temp_dir.path()).await.unwrap(); + handler + .cleanup_snapshot(2, temp_dir.path(), &config.snapshots_dir_prefix) + .await + .unwrap(); // Verify remaining snapshots let mut remaining = get_snapshot_versions(temp_dir.path()); @@ -1113,7 +1119,10 @@ async fn test_cleanup_snapshot_case1() { // Verify files are compressed for version in &[2, 3] { - let path = temp_dir.path().join(format!("{SNAPSHOT_DIR_PREFIX}{version}-1.tar.gz")); + let path = temp_dir.path().join(format!( + "{}/{}-1.tar.gz", + config.snapshots_dir_prefix, version + )); assert!(path.is_file(), "Snapshot file not found: {path:?}",); } } @@ -1124,18 +1133,22 @@ async fn test_cleanup_snapshot_case1() { async fn test_cleanup_snapshot_case2() { let temp_dir = TempDir::new().unwrap(); let sm = MockStateMachine::new(); - create_test_files(&temp_dir, &[3, 4]).await; + let config = snapshot_config(temp_dir.path().to_path_buf()); + create_test_files(&temp_dir, &[3, 4], &config.snapshots_dir_prefix).await; let handler = DefaultStateMachineHandler::::new( 1, 0, 1, Arc::new(sm), - snapshot_config(temp_dir.path().to_path_buf()), + config.clone(), MockSnapshotPolicy::new(), ); - handler.cleanup_snapshot(2, temp_dir.path()).await.unwrap(); + handler + .cleanup_snapshot(2, temp_dir.path(), &config.snapshots_dir_prefix) + .await + .unwrap(); // Verify no deletions let mut remaining = get_snapshot_versions(temp_dir.path()); @@ -1151,29 +1164,37 @@ async fn test_cleanup_snapshot_case2() { async fn test_cleanup_snapshot_case3() { let temp_dir = TempDir::new().unwrap(); let sm = MockStateMachine::new(); + let config = snapshot_config(temp_dir.path().to_path_buf()); // Create valid and invalid directories - create_dir(&temp_dir, &format!("{SNAPSHOT_DIR_PREFIX}1-1",)).await; + create_dir(&temp_dir, &format!("{}1-1", &config.snapshots_dir_prefix)).await; create_dir(&temp_dir, "invalid_format").await; - create_dir(&temp_dir, &format!("{SNAPSHOT_DIR_PREFIX}bad-2-2",)).await; + create_dir( + &temp_dir, + &format!("{}bad-2-2", &config.snapshots_dir_prefix), + ) + .await; let handler = DefaultStateMachineHandler::::new( 1, 0, 1, Arc::new(sm), - snapshot_config(temp_dir.path().to_path_buf()), + config.clone(), MockSnapshotPolicy::new(), ); - handler.cleanup_snapshot(2, temp_dir.path()).await.unwrap(); + handler + .cleanup_snapshot(2, temp_dir.path(), &config.snapshots_dir_prefix) + .await + .unwrap(); //Verify only valid version 1 is deleted let remaining = get_dir_names(temp_dir.path()).await; debug!(?remaining); assert!(remaining.contains(&"invalid_format".into())); - assert!(remaining.contains(&format!("{SNAPSHOT_DIR_PREFIX}bad-2-2",))); - assert!(remaining.contains(&format!("{SNAPSHOT_DIR_PREFIX}1-1",))); + assert!(remaining.contains(&format!("{}bad-2-2", &config.snapshots_dir_prefix))); + assert!(remaining.contains(&format!("{}1-1", &config.snapshots_dir_prefix))); } /// #Case 1: Reject stale term @@ -1480,9 +1501,10 @@ async fn create_test_snapshot( async fn create_test_files( temp_dir: &TempDir, ids: &[u64], + snapshots_dir_prefix: &str, ) { for id in ids { - let file_name = format!("{SNAPSHOT_DIR_PREFIX}{id}-1.tar.gz"); + let file_name = format!("{}{id}-1.tar.gz", snapshots_dir_prefix); let path = temp_dir.path().join(file_name); debug!(?path, "create_test_files"); let mut file = File::create(&path).await.unwrap(); @@ -1511,91 +1533,91 @@ async fn get_dir_names(path: &Path) -> Vec { names } -fn mock_node_with_rpc_service( - db_path: &str, - listen_address: SocketAddr, - is_leader: bool, - shutdown_signal: watch::Receiver<()>, - peers_meta_option: Option>, -) -> Arc> { - let mut node_config = node_config(db_path); - if let Some(peers_meta) = peers_meta_option { - node_config.cluster.initial_cluster = peers_meta; - } - - // Update listen address with passed one - node_config.cluster.listen_address = listen_address; - if !is_leader { - // Make sure no election happens - node_config.raft.election.election_timeout_min = 500000; - node_config.raft.election.election_timeout_max = 1000000 - } - - // Initializing Shutdown Signal - let mut replication_handler = MockReplicationCore::new(); - replication_handler - .expect_handle_raft_request_in_batch() - .returning(|_, _, _, _| { - Ok(AppendResults { - commit_quorum_achieved: false, - learner_progress: HashMap::new(), - peer_updates: HashMap::new(), - }) - }); - let mut election_handler = MockElectionCore::::new(); - election_handler - .expect_check_vote_request_is_legal() - .returning(|_, _, _, _, _| true); - if is_leader { - election_handler - .expect_broadcast_vote_requests() - .returning(|_, _, _, _, _| Ok(())); - election_handler - .expect_handle_vote_request() - .times(1) - .returning(move |_, _, _, _| { - Ok(StateUpdate { - new_voted_for: Some(VotedFor { - voted_for_id: 1, - voted_for_term: 1, - }), - term_update: Some(2), - }) - }); - } else { - // Make sure node is Follower - election_handler.expect_broadcast_vote_requests().returning(|_, _, _, _, _| { - Err(Error::Consensus(ConsensusError::Election( - ElectionError::HigherTerm(100), - ))) - }); - } - // let state_machine_handler = Arc::new(state_machine_handler); - let mut mock_state_machine_handler = MockStateMachineHandler::new(); - mock_state_machine_handler.expect_apply_snapshot_stream_from_leader().returning( - move |_current_term, _stream_request, ack_tx, _| { - let ack_tx = ack_tx.clone(); - tokio::spawn(async move { - // Send final ack - let final_ack = SnapshotAck { - status: ChunkStatus::Accepted as i32, - seq: u32::MAX, - next_requested: 0, - }; - ack_tx.send(final_ack).await.ok(); - }); - Ok(()) - }, - ); - - MockBuilder::new(shutdown_signal) - .with_node_config(node_config) - .with_replication_handler(replication_handler) - .with_election_handler(election_handler) - .with_state_machine_handler(mock_state_machine_handler) - .turn_on_election(is_leader) - .build_node_with_rpc_server() -} +// fn mock_node_with_rpc_service( +// db_path: &str, +// listen_address: SocketAddr, +// is_leader: bool, +// shutdown_signal: watch::Receiver<()>, +// peers_meta_option: Option>, +// ) -> Arc> { +// let mut node_config = node_config(db_path); +// if let Some(peers_meta) = peers_meta_option { +// node_config.cluster.initial_cluster = peers_meta; +// } + +// // Update listen address with passed one +// node_config.cluster.listen_address = listen_address; +// if !is_leader { +// // Make sure no election happens +// node_config.raft.election.election_timeout_min = 500000; +// node_config.raft.election.election_timeout_max = 1000000 +// } + +// // Initializing Shutdown Signal +// let mut replication_handler = MockReplicationCore::new(); +// replication_handler +// .expect_handle_raft_request_in_batch() +// .returning(|_, _, _, _| { +// Ok(AppendResults { +// commit_quorum_achieved: false, +// learner_progress: HashMap::new(), +// peer_updates: HashMap::new(), +// }) +// }); +// let mut election_handler = MockElectionCore::::new(); +// election_handler +// .expect_check_vote_request_is_legal() +// .returning(|_, _, _, _, _| true); +// if is_leader { +// election_handler +// .expect_broadcast_vote_requests() +// .returning(|_, _, _, _, _| Ok(())); +// election_handler +// .expect_handle_vote_request() +// .times(1) +// .returning(move |_, _, _, _| { +// Ok(StateUpdate { +// new_voted_for: Some(VotedFor { +// voted_for_id: 1, +// voted_for_term: 1, +// }), +// term_update: Some(2), +// }) +// }); +// } else { +// // Make sure node is Follower +// election_handler.expect_broadcast_vote_requests().returning(|_, _, _, _, _| { +// Err(Error::Consensus(ConsensusError::Election( +// ElectionError::HigherTerm(100), +// ))) +// }); +// } +// // let state_machine_handler = Arc::new(state_machine_handler); +// let mut mock_state_machine_handler = MockStateMachineHandler::new(); +// mock_state_machine_handler.expect_apply_snapshot_stream_from_leader().returning( +// move |_current_term, _stream_request, ack_tx, _| { +// let ack_tx = ack_tx.clone(); +// tokio::spawn(async move { +// // Send final ack +// let final_ack = SnapshotAck { +// status: ChunkStatus::Accepted as i32, +// seq: u32::MAX, +// next_requested: 0, +// }; +// ack_tx.send(final_ack).await.ok(); +// }); +// Ok(()) +// }, +// ); + +// MockBuilder::new(shutdown_signal) +// .with_node_config(node_config) +// .with_replication_handler(replication_handler) +// .with_election_handler(election_handler) +// .with_state_machine_handler(mock_state_machine_handler) +// .turn_on_election(is_leader) +// .build_node_with_rpc_server() +// } // Update test_load_snapshot_data_case1_single_file_single_chunk #[tokio::test] diff --git a/d-engine-core/src/state_machine_handler/mod.rs b/d-engine-core/src/state_machine_handler/mod.rs index 1e7cd986..bcedf98c 100644 --- a/d-engine-core/src/state_machine_handler/mod.rs +++ b/d-engine-core/src/state_machine_handler/mod.rs @@ -125,6 +125,7 @@ where &self, before_version: u64, snapshot_dir: &std::path::Path, + snapshot_dir_prefix: &str, ) -> crate::Result<()>; /// Validates if a log purge request from leader is authorized diff --git a/d-engine-core/src/state_machine_handler/snapshot_policy/composite_test.rs b/d-engine-core/src/state_machine_handler/snapshot_policy/composite_test.rs index 1f78f7b0..f650f4a9 100644 --- a/d-engine-core/src/state_machine_handler/snapshot_policy/composite_test.rs +++ b/d-engine-core/src/state_machine_handler/snapshot_policy/composite_test.rs @@ -3,19 +3,16 @@ use std::time::Duration; use tracing_test::traced_test; use super::*; -use crate::core::state_machine_handler::snapshot_policy::SnapshotContext; -use crate::core::state_machine_handler::snapshot_policy::SnapshotPolicy; +use crate::state_machine_handler::snapshot_policy::SnapshotContext; +use crate::state_machine_handler::snapshot_policy::SnapshotPolicy; use d_engine_proto::common::LogId; use d_engine_proto::common::NodeRole::Follower; use d_engine_proto::common::NodeRole::Leader; -const LEADER_ROLE: i32 = Leader.into; -const FOLLOWER_ROLE: i32 = Follower.into(); - /// Creates a leader context for testing fn leader_ctx(last_applied_index: u64) -> SnapshotContext { SnapshotContext { - role: LEADER_ROLE, + role: Leader.into(), last_included: LogId { term: 1, index: 0 }, last_applied: LogId { term: 1, @@ -28,7 +25,7 @@ fn leader_ctx(last_applied_index: u64) -> SnapshotContext { /// Creates a follower context for testing fn follower_ctx(last_applied_index: u64) -> SnapshotContext { SnapshotContext { - role: FOLLOWER_ROLE, + role: Follower.into(), last_included: LogId { term: 1, index: 0 }, last_applied: LogId { term: 1, diff --git a/d-engine-core/src/state_machine_handler/snapshot_policy/log_size_test.rs b/d-engine-core/src/state_machine_handler/snapshot_policy/log_size_test.rs index a552b152..c448752f 100644 --- a/d-engine-core/src/state_machine_handler/snapshot_policy/log_size_test.rs +++ b/d-engine-core/src/state_machine_handler/snapshot_policy/log_size_test.rs @@ -3,11 +3,11 @@ use std::time::Duration; use std::time::Instant; use super::SnapshotContext; -use crate::Leader; use crate::LogSizePolicy; use crate::SnapshotPolicy; use d_engine_proto::common::LogId; use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeRole::Leader; // Test context builder for better test readability fn test_context( @@ -32,28 +32,28 @@ fn test_context( #[test] fn triggers_when_log_size_exceeds_threshold() { let policy = LogSizePolicy::new(1000, Duration::from_secs(1)); - let ctx = test_context(1500, 500, Leader); + let ctx = test_context(1500, 500, Leader.into()); assert!(policy.should_trigger(&ctx)); } #[test] fn does_not_trigger_below_threshold() { let policy = LogSizePolicy::new(1000, Duration::from_secs(1)); - let ctx = test_context(1499, 500, Leader); + let ctx = test_context(1499, 500, Leader.into()); assert!(!policy.should_trigger(&ctx)); } #[test] fn triggers_at_exact_threshold() { let policy = LogSizePolicy::new(1000, Duration::from_secs(1)); - let ctx = test_context(1500, 500, Leader); + let ctx = test_context(1500, 500, Leader.into()); assert!(policy.should_trigger(&ctx)); } #[test] fn respects_cooldown_period() { let policy = LogSizePolicy::new(100, Duration::from_secs(1)); - let mut ctx = test_context(200, 100, Leader); + let mut ctx = test_context(200, 100, Leader.into()); // Initial check should trigger assert!(policy.should_trigger(&ctx)); @@ -66,7 +66,7 @@ fn respects_cooldown_period() { #[test] fn resets_after_cooldown_period() { let policy = LogSizePolicy::new(100, Duration::from_millis(100)); - let ctx = test_context(200, 100, Leader); + let ctx = test_context(200, 100, Leader.into()); // First trigger assert!(policy.should_trigger(&ctx)); @@ -79,7 +79,7 @@ fn resets_after_cooldown_period() { #[test] fn handles_concurrent_checks_with_cooldown() { let policy = Arc::new(LogSizePolicy::new(100, Duration::from_secs(1))); - let ctx = test_context(200, 100, Leader); + let ctx = test_context(200, 100, Leader.into()); let handles: Vec<_> = (0..10) .map(|_| { @@ -96,14 +96,14 @@ fn handles_concurrent_checks_with_cooldown() { #[test] fn non_leader_never_triggers() { let policy = LogSizePolicy::new(100, Duration::ZERO); - let ctx = test_context(200, 100, Follower); + let ctx = test_context(200, 100, Follower.into()); assert!(!policy.should_trigger(&ctx)); } #[test] fn dynamic_threshold_adjustment() { let policy = LogSizePolicy::new(1000, Duration::from_secs(1)); - let ctx = test_context(1200, 500, Leader); + let ctx = test_context(1200, 500, Leader.into()); // Initial threshold not met assert!(!policy.should_trigger(&ctx)); @@ -126,7 +126,7 @@ fn handles_term_regression() { term: 3, }, // Higher than current term current_term: 2, - role: Leader, + role: Leader.into(), }; assert!(!policy.should_trigger(&ctx)); @@ -135,7 +135,7 @@ fn handles_term_regression() { #[test] fn high_frequency_performance() { let policy = LogSizePolicy::new(1000, Duration::from_millis(100)); - let ctx = test_context(1500, 500, Leader); + let ctx = test_context(1500, 500, Leader.into()); let start = Instant::now(); let mut trigger_count = 0; diff --git a/d-engine-core/src/state_machine_handler/snapshot_policy/time_based_test.rs b/d-engine-core/src/state_machine_handler/snapshot_policy/time_based_test.rs index a3d17b45..7418c2e7 100644 --- a/d-engine-core/src/state_machine_handler/snapshot_policy/time_based_test.rs +++ b/d-engine-core/src/state_machine_handler/snapshot_policy/time_based_test.rs @@ -4,10 +4,10 @@ use tokio::time; use tracing_test::traced_test; use super::*; -use crate::Leader; -use crate::core::state_machine_handler::snapshot_policy::SnapshotContext; -use crate::core::state_machine_handler::snapshot_policy::SnapshotPolicy; +use crate::state_machine_handler::snapshot_policy::SnapshotContext; +use crate::state_machine_handler::snapshot_policy::SnapshotPolicy; use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Leader; #[tokio::test] #[traced_test] @@ -19,7 +19,7 @@ async fn test_should_trigger_after_interval() { // Create context (leader) let ctx = SnapshotContext { - role: Leader, // Assuming Leader is defined elsewhere + role: Leader.into(), // Assuming Leader is defined elsewhere last_included: LogId { term: 1, index: 1 }, last_applied: LogId { term: 1, index: 10 }, current_term: 1, @@ -45,7 +45,7 @@ async fn test_should_not_trigger_before_interval() { // Create context (leader) let ctx = SnapshotContext { - role: Leader, + role: Leader.into(), last_included: LogId { term: 1, index: 1 }, last_applied: LogId { term: 1, index: 10 }, current_term: 1, @@ -67,7 +67,7 @@ async fn test_reset_timer_works() { policy.reset_timer(); let ctx = SnapshotContext { - role: Leader, + role: Leader.into(), last_included: LogId { term: 1, index: 1 }, last_applied: LogId { term: 1, index: 10 }, current_term: 1, diff --git a/d-engine-core/src/storage/buffered/buffered_raft_log_test.rs b/d-engine-core/src/storage/buffered/buffered_raft_log_test.rs index 7aa8c6d1..1b8f4530 100644 --- a/d-engine-core/src/storage/buffered/buffered_raft_log_test.rs +++ b/d-engine-core/src/storage/buffered/buffered_raft_log_test.rs @@ -15,7 +15,6 @@ use tracing::debug; use tracing_test::traced_test; use super::*; -use crate::BufferedRaftLog; use crate::FileStorageEngine; use crate::FlushPolicy; use crate::LogStore; @@ -2958,7 +2957,7 @@ mod save_load_hard_state_tests { use crate::LogStore; use crate::MetaStore; use crate::StorageEngine; - use d_engine_proto::election::VotedFor; + use d_engine_proto::server::election::VotedFor; /// Test that hard state operations use the meta tree and not the log tree #[tokio::test] diff --git a/d-engine-core/src/storage/raft_log.rs b/d-engine-core/src/storage/raft_log.rs index 8ac503ce..52842119 100644 --- a/d-engine-core/src/storage/raft_log.rs +++ b/d-engine-core/src/storage/raft_log.rs @@ -125,38 +125,3 @@ pub trait RaftLog: Send + Sync + 'static { hard_state: &crate::HardState, ) -> Result<()>; } - -impl EntryPayload { - #[allow(dead_code)] - #[inline] - pub(crate) fn is_config(&self) -> bool { - match self.payload { - Some(Payload::Command(_)) => false, - Some(Payload::Config(_)) => true, - Some(Payload::Noop(_)) => false, - None => false, - } - } - - #[allow(dead_code)] - #[inline] - pub(crate) fn is_command(&self) -> bool { - match self.payload { - Some(Payload::Command(_)) => true, - Some(Payload::Config(_)) => false, - Some(Payload::Noop(_)) => false, - None => false, - } - } - - #[allow(dead_code)] - #[inline] - pub(crate) fn is_noop(&self) -> bool { - match self.payload { - Some(Payload::Command(_)) => false, - Some(Payload::Config(_)) => false, - Some(Payload::Noop(_)) => true, - None => false, - } - } -} diff --git a/d-engine-core/src/storage/state_machine.rs b/d-engine-core/src/storage/state_machine.rs index 508f6475..94e58de0 100644 --- a/d-engine-core/src/storage/state_machine.rs +++ b/d-engine-core/src/storage/state_machine.rs @@ -1,4 +1,4 @@ -#![doc = include_str!("../docs/server_guide/customize-state-machine.md")] +#![doc = include_str!("../../../d-engine-docs/src/docs/server_guide/customize-state-machine.md")] use bytes::Bytes; #[cfg(test)] @@ -157,22 +157,3 @@ pub trait StateMachine: Send + Sync + 'static { /// Async operation as it may involve cleaning up files and data. async fn reset(&self) -> Result<(), Error>; } - -impl SnapshotMetadata { - pub fn checksum_array(&self) -> Result<[u8; 32], Error> { - if self.checksum.len() == 32 { - let mut array = [0u8; 32]; - array.copy_from_slice(&self.checksum); - Ok(array) - } else { - Err(ConvertError::ConversionFailure("Invalid checksum length".to_string()).into()) - } - } - - pub fn set_checksum_array( - &mut self, - array: [u8; 32], - ) { - self.checksum = Bytes::copy_from_slice(&array); - } -} diff --git a/d-engine-core/src/storage/storage_engine.rs b/d-engine-core/src/storage/storage_engine.rs index 755b7ff2..2f31f3c7 100644 --- a/d-engine-core/src/storage/storage_engine.rs +++ b/d-engine-core/src/storage/storage_engine.rs @@ -1,4 +1,4 @@ -#![doc = include_str!("../docs/server_guide/customize-storage-engine.md")] +#![doc = include_str!("../../../d-engine-docs/src/docs/server_guide/customize-storage-engine.md")] use std::ops::RangeInclusive; use std::sync::Arc; diff --git a/d-engine-core/src/storage/storage_engine_test.rs b/d-engine-core/src/storage/storage_engine_test.rs index fabb0f2e..d636380c 100644 --- a/d-engine-core/src/storage/storage_engine_test.rs +++ b/d-engine-core/src/storage/storage_engine_test.rs @@ -13,7 +13,7 @@ use crate::storage::StorageEngine; use d_engine_proto::client::write_command::Insert; use d_engine_proto::common::Entry; use d_engine_proto::common::LogId; -use d_engine_proto::election::VotedFor; +use d_engine_proto::server::election::VotedFor; /// Test suite for StorageEngine implementations /// diff --git a/src/test_utils/common.rs b/d-engine-core/src/test_utils/common.rs similarity index 98% rename from src/test_utils/common.rs rename to d-engine-core/src/test_utils/common.rs index aac88fb6..ff501814 100644 --- a/src/test_utils/common.rs +++ b/d-engine-core/src/test_utils/common.rs @@ -7,18 +7,18 @@ use bytes::Bytes; use bytes::BytesMut; use prost::Message; -use crate::alias::ROF; -use crate::convert::safe_kv_bytes; use crate::FileStorageEngine; use crate::MockStateMachine; use crate::RaftLog; use crate::RaftTypeConfig; use crate::SnapshotConfig; +use crate::alias::ROF; +use crate::convert::safe_kv_bytes; use d_engine_proto::client::WriteCommand; -use d_engine_proto::common::membership_change::Change; use d_engine_proto::common::AddNode; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::membership_change::Change; pub fn create_mixed_entries() -> Vec { let config_entry = Entry { @@ -132,5 +132,6 @@ pub fn snapshot_config(snapshots_dir: PathBuf) -> SnapshotConfig { snapshot_push_max_retry: 1, push_timeout_in_ms: 100, enable: true, + snapshots_dir_prefix: "snapshot-".to_string(), } } diff --git a/src/test_utils/entry_builder.rs b/d-engine-core/src/test_utils/entry_builder.rs similarity index 100% rename from src/test_utils/entry_builder.rs rename to d-engine-core/src/test_utils/entry_builder.rs diff --git a/src/test_utils/integration/mod.rs b/d-engine-core/src/test_utils/integration/mod.rs similarity index 97% rename from src/test_utils/integration/mod.rs rename to d-engine-core/src/test_utils/integration/mod.rs index 222da708..217f6cf5 100644 --- a/src/test_utils/integration/mod.rs +++ b/d-engine-core/src/test_utils/integration/mod.rs @@ -46,13 +46,6 @@ pub(crate) use snapshot::*; use tokio::sync::watch; use super::generate_insert_commands; -use crate::alias::MOF; -use crate::alias::ROF; -use crate::alias::SMOF; -use crate::alias::TROF; -use crate::grpc::grpc_transport::GrpcTransport; -use crate::test_utils::mock_state_machine; -use crate::test_utils::MockTypeConfig; use crate::BufferedRaftLog; use crate::DefaultStateMachineHandler; use crate::ElectionHandler; @@ -72,6 +65,13 @@ use crate::ReplicationHandler; use crate::StateMachine; use crate::StorageEngine; use crate::TypeConfig; +use crate::alias::MOF; +use crate::alias::ROF; +use crate::alias::SMOF; +use crate::alias::TROF; +use crate::grpc::grpc_transport::GrpcTransport; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::mock_state_machine; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; use d_engine_proto::common::NodeStatus; @@ -111,8 +111,10 @@ pub fn setup_raft_components( ) -> TestContext> { println!("Test setup_raft_components ..."); - std::env::remove_var("CONFIG_PATH"); - std::env::remove_var("RAFT__INITIAL_CLUSTER"); + unsafe { + std::env::remove_var("CONFIG_PATH"); + std::env::remove_var("RAFT__INITIAL_CLUSTER"); + } //start from fresh let id = 1; @@ -246,7 +248,7 @@ pub(crate) async fn insert_state_machine( } } -pub(crate) fn node_config(db_path: &str) -> RaftNodeConfig { +pub fn node_config(db_path: &str) -> RaftNodeConfig { let mut s = RaftNodeConfig::new().expect("RaftNodeConfig should be inited successfully."); s.cluster.db_root_dir = PathBuf::from(db_path); s diff --git a/src/test_utils/integration/snapshot.rs b/d-engine-core/src/test_utils/integration/snapshot.rs similarity index 100% rename from src/test_utils/integration/snapshot.rs rename to d-engine-core/src/test_utils/integration/snapshot.rs diff --git a/d-engine-core/src/test_utils/mock/mock_raft_builder.rs b/d-engine-core/src/test_utils/mock/mock_raft_builder.rs new file mode 100644 index 00000000..66436600 --- /dev/null +++ b/d-engine-core/src/test_utils/mock/mock_raft_builder.rs @@ -0,0 +1,420 @@ +use std::path::Path; +use std::sync::Arc; +use std::sync::atomic::AtomicBool; + +use bytes::Bytes; +use tokio::sync::Mutex; +use tokio::sync::mpsc; +use tokio::sync::watch; +use tracing::error; +use tracing::trace; + +use super::MockTypeConfig; +use crate::ElectionConfig; +use crate::MockElectionCore; +use crate::MockMembership; +use crate::MockPurgeExecutor; +use crate::MockRaftLog; +use crate::MockReplicationCore; +use crate::MockStateMachine; +use crate::MockStateMachineHandler; +use crate::MockTransport; +use crate::Raft; +use crate::RaftConfig; +use crate::RaftContext; +use crate::RaftCoreHandlers; +use crate::RaftEvent; +use crate::RaftLog; +use crate::RaftNodeConfig; +use crate::RaftRole; +use crate::RaftStorageHandles; +use crate::RoleEvent; +use crate::SignalParams; +use crate::StateMachine; +use crate::follower_state::FollowerState; +use d_engine_proto::common::LogId; +use d_engine_proto::server::cluster::ClusterMembership; + +pub struct MockBuilder { + pub id: Option, + pub role: Option>, + pub raft_log: Option, + pub state_machine: Option>, + pub transport: Option>, + pub membership: Option>>, + pub purge_executor: Option, + pub election_handler: Option>, + pub replication_handler: Option>, + pub state_machine_handler: Option>>, + pub node_config: Option, + pub turn_on_election: Option, + shutdown_signal: watch::Receiver<()>, + + pub(crate) event_tx: Option>, + pub(crate) event_rx: Option>, + + pub(crate) role_tx: Option>, + pub(crate) role_rx: Option>, +} + +impl MockBuilder { + pub fn new(shutdown_signal: watch::Receiver<()>) -> Self { + Self { + id: None, + role: None, + raft_log: None, + state_machine: None, + transport: None, + membership: None, + purge_executor: None, + election_handler: None, + replication_handler: None, + state_machine_handler: None, + node_config: None, + turn_on_election: None, + shutdown_signal, + + role_tx: None, + role_rx: None, + event_tx: None, + event_rx: None, + } + } + + pub(crate) fn build_context(self) -> RaftContext { + let ( + raft_log, + state_machine, + transport, + election_handler, + replication_handler, + state_machine_handler, + membership, + purge_executor, + node_config, + ) = ( + Arc::new(self.raft_log.unwrap_or_else(mock_raft_log)), + self.state_machine.unwrap_or_else(|| Arc::new(mock_state_machine())), + Arc::new(self.transport.unwrap_or_else(mock_transport)), + self.election_handler.unwrap_or_else(mock_election_core), + self.replication_handler.unwrap_or_else(mock_replication_handler), + self.state_machine_handler + .unwrap_or_else(|| Arc::new(mock_state_machine_handler())), + self.membership.unwrap_or_else(|| Arc::new(mock_membership())), + self.purge_executor.unwrap_or_else(mock_purge_exewcutor), + self.node_config.unwrap_or_else(|| { + RaftNodeConfig::new().expect("Should succeed to init RaftNodeConfig") + }), + ); + + let storage = RaftStorageHandles { + raft_log, + state_machine, + }; + let handlers = RaftCoreHandlers { + election_handler, + replication_handler, + state_machine_handler, + purge_executor: Arc::new(purge_executor), + }; + mock_raft_context_internal(1, storage, transport, membership, handlers, node_config) + } + + pub fn build_raft(self) -> Raft { + let (role_tx, role_rx) = mpsc::unbounded_channel(); + let (event_tx, event_rx) = mpsc::channel(10); + let ( + id, + raft_log, + state_machine, + transport, + election_handler, + replication_handler, + state_machine_handler, + membership, + purge_executor, + node_config, + role_tx, + role_rx, + event_tx, + event_rx, + ) = ( + self.id.unwrap_or(1), + self.raft_log.unwrap_or_else(mock_raft_log), + self.state_machine.unwrap_or_else(|| Arc::new(mock_state_machine())), + self.transport.unwrap_or_else(mock_transport), + self.election_handler.unwrap_or_else(mock_election_core), + self.replication_handler.unwrap_or_else(mock_replication_handler), + self.state_machine_handler + .unwrap_or_else(|| Arc::new(mock_state_machine_handler())), + self.membership.unwrap_or_else(|| Arc::new(mock_membership())), + self.purge_executor.unwrap_or_else(mock_purge_exewcutor), + self.node_config.unwrap_or_else(|| { + RaftNodeConfig::new().expect("Should succeed to init RaftNodeConfig") + }), + self.role_tx.unwrap_or(role_tx), + self.role_rx.unwrap_or(role_rx), + self.event_tx.unwrap_or(event_tx), + self.event_rx.unwrap_or(event_rx), + ); + trace!(node_config.raft.election.election_timeout_min, "build_raft"); + + let election_config = { + if self.turn_on_election.unwrap_or(true) { + ElectionConfig { + election_timeout_min: 1, + election_timeout_max: 2, + ..node_config.raft.election + } + } else { + node_config.raft.election + } + }; + let arc_node_config = Arc::new(RaftNodeConfig { + raft: RaftConfig { + election: election_config, + ..node_config.raft + }, + ..node_config + }); + + let role = self.role.unwrap_or(RaftRole::Follower(Box::new(FollowerState::new( + id, + arc_node_config.clone(), + raft_log.load_hard_state().expect("Failed to load hard state"), + Some(state_machine.last_applied().index), + )))); + + Raft::new( + id, + role, + RaftStorageHandles:: { + raft_log: Arc::new(raft_log), + state_machine, + }, + transport, + RaftCoreHandlers:: { + election_handler, + replication_handler, + state_machine_handler, + purge_executor: Arc::new(purge_executor), + }, + membership, + SignalParams { + role_tx, + role_rx, + event_tx, + event_rx, + shutdown_signal: self.shutdown_signal, + }, + arc_node_config.clone(), + ) + } + + pub fn id( + mut self, + id: u32, + ) -> Self { + self.id = Some(id); + self + } + pub fn with_role( + mut self, + role: RaftRole, + ) -> Self { + self.role = Some(role); + self + } + pub fn with_raft_log( + mut self, + raft_log: MockRaftLog, + ) -> Self { + self.raft_log = Some(raft_log); + self + } + pub fn with_state_machine( + mut self, + sm: MockStateMachine, + ) -> Self { + self.state_machine = Some(Arc::new(sm)); + self + } + + pub fn with_transport( + mut self, + transport: MockTransport, + ) -> Self { + self.transport = Some(transport); + self + } + + pub fn with_membership( + mut self, + membership: MockMembership, + ) -> Self { + self.membership = Some(Arc::new(membership)); + self + } + + pub fn with_election_handler( + mut self, + election_handler: MockElectionCore, + ) -> Self { + self.election_handler = Some(election_handler); + self + } + + pub fn with_replication_handler( + mut self, + replication_handler: MockReplicationCore, + ) -> Self { + self.replication_handler = Some(replication_handler); + self + } + + pub fn with_state_machine_handler( + mut self, + state_machine_handler: MockStateMachineHandler, + ) -> Self { + self.state_machine_handler = Some(Arc::new(state_machine_handler)); + self + } + + pub fn with_node_config( + mut self, + node_config: RaftNodeConfig, + ) -> Self { + self.node_config = Some(node_config); + self + } + + pub fn with_db_path( + mut self, + db_root_dir: impl AsRef, + ) -> Self { + let mut node_config = + RaftNodeConfig::new().expect("Should succeed to init RaftNodeConfig."); + node_config.cluster.db_root_dir = db_root_dir.as_ref().to_path_buf(); + self.node_config = Some(node_config); + self + } + + pub fn turn_on_election( + mut self, + is_on: bool, + ) -> Self { + self.turn_on_election = Some(is_on); + self + } +} + +pub fn mock_raft_log() -> MockRaftLog { + let mut raft_log = MockRaftLog::new(); + raft_log.expect_last_entry_id().returning(|| 0); + raft_log.expect_last_log_id().returning(|| None); + raft_log.expect_flush().returning(|| Ok(())); + raft_log.expect_load_hard_state().returning(|| Ok(None)); + raft_log.expect_save_hard_state().returning(|_| Ok(())); + raft_log +} + +pub fn mock_transport() -> MockTransport { + MockTransport::new() +} + +pub fn mock_election_core() -> MockElectionCore { + let mut election_handler = MockElectionCore::new(); + election_handler + .expect_broadcast_vote_requests() + .returning(|_, _, _, _, _| Ok(())); + election_handler +} + +pub fn mock_replication_handler() -> MockReplicationCore { + MockReplicationCore::new() +} + +pub fn mock_state_machine() -> MockStateMachine { + let mut mock = MockStateMachine::new(); + + mock.expect_start().returning(|| Ok(())); + mock.expect_stop().returning(|| Ok(())); + mock.expect_is_running().returning(|| true); + + mock.expect_get().returning(|_| Ok(None)); + mock.expect_entry_term().returning(|_| None); + mock.expect_apply_chunk().returning(|_| Ok(())); + mock.expect_len().returning(|| 0); + + mock.expect_update_last_applied().returning(|_| ()); + mock.expect_last_applied().return_const(LogId::default()); + mock.expect_persist_last_applied().returning(|_| Ok(())); + + mock.expect_update_last_snapshot_metadata().returning(|_| Ok(())); + mock.expect_snapshot_metadata().returning(|| None); + mock.expect_persist_last_snapshot_metadata().returning(|_| Ok(())); + + mock.expect_apply_snapshot_from_file().returning(|_, _| Ok(())); + mock.expect_generate_snapshot_data() + .returning(|_, _| Ok(Bytes::copy_from_slice(&[0u8; 32]))); + + mock.expect_save_hard_state().returning(|| Ok(())); + mock.expect_flush().returning(|| Ok(())); + + mock +} + +pub fn mock_state_machine_handler() -> MockStateMachineHandler { + let mut state_machine_handler = MockStateMachineHandler::new(); + state_machine_handler.expect_update_pending().returning(|_| {}); + state_machine_handler.expect_read_from_state_machine().returning(|_| None); + state_machine_handler +} + +pub fn mock_purge_exewcutor() -> MockPurgeExecutor { + let mut purge_exewcutor = MockPurgeExecutor::new(); + purge_exewcutor.expect_execute_purge().returning(|_| Ok(())); + purge_exewcutor +} +pub fn mock_membership() -> MockMembership { + let mut membership = MockMembership::new(); + membership.expect_can_rejoin().returning(|_, _| Ok(())); + membership.expect_pre_warm_connections().returning(|| Ok(())); + membership.expect_voters().returning(Vec::new); + membership.expect_replication_peers().returning(Vec::new); + membership.expect_members().returning(Vec::new); + membership.expect_reset_leader().returning(|| Ok(())); + membership.expect_update_node_role().returning(|_, _| Ok(())); + membership.expect_mark_leader_id().returning(|_| Ok(())); + membership.expect_check_cluster_is_ready().returning(|| Ok(())); + membership + .expect_retrieve_cluster_membership_config() + .returning(|| ClusterMembership { + version: 1, + nodes: vec![], + }); + membership.expect_get_zombie_candidates().returning(Vec::new); + membership.expect_get_peers_id_with_condition().returning(|_| vec![]); + membership +} + +fn mock_raft_context_internal( + id: u32, + storage: RaftStorageHandles, + transport: Arc>, + membership: Arc>, + handlers: RaftCoreHandlers, + node_config: RaftNodeConfig, +) -> RaftContext { + RaftContext { + node_id: id, + storage, + + transport, + membership, + + handlers, + + node_config: Arc::new(node_config), + } +} diff --git a/src/test_utils/mock/mock_rpc.rs b/d-engine-core/src/test_utils/mock/mock_rpc.rs similarity index 100% rename from src/test_utils/mock/mock_rpc.rs rename to d-engine-core/src/test_utils/mock/mock_rpc.rs diff --git a/src/test_utils/mock/mock_rpc_service.rs b/d-engine-core/src/test_utils/mock/mock_rpc_service.rs similarity index 98% rename from src/test_utils/mock/mock_rpc_service.rs rename to d-engine-core/src/test_utils/mock/mock_rpc_service.rs index c0407b40..77337377 100644 --- a/src/test_utils/mock/mock_rpc_service.rs +++ b/d-engine-core/src/test_utils/mock/mock_rpc_service.rs @@ -3,9 +3,9 @@ use std::sync::Arc; use tokio::net::TcpListener; use tokio::sync::oneshot; +use tonic::Status; use tonic::codec::CompressionEncoding; use tonic::transport::Channel; -use tonic::Status; use tonic_health::server::health_reporter; use tracing::debug; use tracing::info; @@ -13,21 +13,21 @@ use tracing::info; use super::MockRpcService; use crate::Result; use crate::StorageError; -use crate::LEADER; -use d_engine_proto::client::raft_client_service_server::RaftClientServiceServer; use d_engine_proto::client::ClientResponse; +use d_engine_proto::client::raft_client_service_server::RaftClientServiceServer; +use d_engine_proto::common::NodeRole::Leader; use d_engine_proto::common::NodeStatus; -use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementServiceServer; use d_engine_proto::server::cluster::ClusterMembership; use d_engine_proto::server::cluster::JoinResponse; use d_engine_proto::server::cluster::NodeMeta; -use d_engine_proto::server::election::raft_election_service_server::RaftElectionServiceServer; +use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementServiceServer; use d_engine_proto::server::election::VoteResponse; -use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationServiceServer; +use d_engine_proto::server::election::raft_election_service_server::RaftElectionServiceServer; use d_engine_proto::server::replication::AppendEntriesResponse; -use d_engine_proto::server::storage::snapshot_service_server::SnapshotServiceServer; +use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationServiceServer; use d_engine_proto::server::storage::PurgeLogResponse; use d_engine_proto::server::storage::SnapshotResponse; +use d_engine_proto::server::storage::snapshot_service_server::SnapshotServiceServer; // ports for unit tests pub(crate) const MOCK_STATE_MACHINE_HANDLER_PORT_BASE: u32 = 60400; @@ -186,7 +186,7 @@ impl MockNode { version: 1, nodes: vec![NodeMeta { id: 1, - role: LEADER, + role: Leader.into(), address: format!("127.0.0.1:{port}",), status: NodeStatus::Active.into(), }], @@ -229,7 +229,7 @@ impl MockNode { version: 1, nodes: vec![NodeMeta { id: 1, - role: LEADER, + role: Leader.into(), address: format!("127.0.0.1:{port}",), status: NodeStatus::Active.into(), }], @@ -260,7 +260,7 @@ impl MockNode { version: 1, nodes: vec![NodeMeta { id: 1, - role: LEADER, + role: Leader.into(), address: format!("127.0.0.1:{port}",), status: NodeStatus::Active.into(), }], @@ -291,7 +291,7 @@ impl MockNode { version: 1, nodes: vec![NodeMeta { id: 1, - role: LEADER, + role: Leader.into(), address: format!("127.0.0.1:{port}",), status: NodeStatus::Active.into(), }], diff --git a/src/test_utils/mock/mock_storage_engine.rs b/d-engine-core/src/test_utils/mock/mock_storage_engine.rs similarity index 100% rename from src/test_utils/mock/mock_storage_engine.rs rename to d-engine-core/src/test_utils/mock/mock_storage_engine.rs diff --git a/src/test_utils/mock/mod.rs b/d-engine-core/src/test_utils/mock/mod.rs similarity index 69% rename from src/test_utils/mock/mod.rs rename to d-engine-core/src/test_utils/mock/mod.rs index 69120100..4a20dded 100644 --- a/src/test_utils/mock/mod.rs +++ b/d-engine-core/src/test_utils/mock/mod.rs @@ -38,53 +38,24 @@ //! //! [mockall]: https://docs.rs/mockall/latest/mockall/ -mod mock_builder; +mod mock_raft_builder; mod mock_rpc; mod mock_rpc_service; mod mock_storage_engine; -pub use mock_builder::*; +pub use mock_raft_builder::*; pub use mock_rpc::*; pub use mock_rpc_service::*; pub use mock_storage_engine::*; -use tokio::sync::watch; -use super::node_config; use super::MockTypeConfig; -use crate::Node; +use super::node_config; +use tokio::sync::watch; + use crate::Raft; use crate::RaftContext; use d_engine_proto::server::cluster::NodeMeta; -pub(crate) fn mock_node( - db_path: &str, - shutdown_signal: watch::Receiver<()>, - peers_meta_option: Option>, -) -> Node { - let mut node_config = node_config(db_path); - if let Some(peers_meta) = peers_meta_option { - node_config.cluster.initial_cluster = peers_meta; - } - // Initializing Shutdown Signal - // let (graceful_tx, graceful_rx) = watch::channel(()); - MockBuilder::new(shutdown_signal).with_node_config(node_config).build_node() -} - -pub(crate) fn mock_raft( - db_path: &str, - shutdown_signal: watch::Receiver<()>, - peers_meta_option: Option>, -) -> Raft { - let mut node_config = node_config(db_path); - // Set batch_threshold=0, means the replication will be triggered immediatelly. - node_config.raft.replication.rpc_append_entries_in_batch_threshold = 0; - if let Some(peers_meta) = peers_meta_option { - node_config.cluster.initial_cluster = peers_meta; - } - - MockBuilder::new(shutdown_signal).with_node_config(node_config).build_raft() -} - pub(crate) fn mock_raft_context( db_path: &str, shutdown_signal: watch::Receiver<()>, diff --git a/src/test_utils/mock_type_config.rs b/d-engine-core/src/test_utils/mock_type_config.rs similarity index 100% rename from src/test_utils/mock_type_config.rs rename to d-engine-core/src/test_utils/mock_type_config.rs diff --git a/src/test_utils/mod.rs b/d-engine-core/src/test_utils/mod.rs similarity index 100% rename from src/test_utils/mod.rs rename to d-engine-core/src/test_utils/mod.rs diff --git a/d-engine-core/src/utils/cluster.rs b/d-engine-core/src/utils/cluster.rs index a2ab7170..ce6615a6 100644 --- a/d-engine-core/src/utils/cluster.rs +++ b/d-engine-core/src/utils/cluster.rs @@ -64,20 +64,20 @@ pub fn error( #[inline] pub fn is_follower(role_i32: i32) -> bool { - role_i32 == Follower + role_i32 == Follower.into() } #[inline] pub fn is_candidate(role_i32: i32) -> bool { - role_i32 == Candidate + role_i32 == Candidate.into() } #[inline] pub fn is_leader(role_i32: i32) -> bool { - role_i32 == Leader + role_i32 == Leader.into() } #[inline] pub fn is_learner(role_i32: i32) -> bool { - role_i32 == Learner + role_i32 == Learner.into() } diff --git a/src/utils/convert.rs b/d-engine-core/src/utils/convert.rs similarity index 100% rename from src/utils/convert.rs rename to d-engine-core/src/utils/convert.rs diff --git a/src/utils/convert_test.rs b/d-engine-core/src/utils/convert_test.rs similarity index 100% rename from src/utils/convert_test.rs rename to d-engine-core/src/utils/convert_test.rs diff --git a/src/utils/file_io.rs b/d-engine-core/src/utils/file_io.rs similarity index 100% rename from src/utils/file_io.rs rename to d-engine-core/src/utils/file_io.rs diff --git a/src/utils/file_io_test.rs b/d-engine-core/src/utils/file_io_test.rs similarity index 100% rename from src/utils/file_io_test.rs rename to d-engine-core/src/utils/file_io_test.rs diff --git a/d-engine-core/src/utils/mod.rs b/d-engine-core/src/utils/mod.rs index daed9263..fda91e2b 100644 --- a/d-engine-core/src/utils/mod.rs +++ b/d-engine-core/src/utils/mod.rs @@ -1,4 +1,22 @@ -mod cluster; -mod scoped_timer; +#[allow(dead_code)] +pub mod cluster; +#[allow(dead_code)] +pub mod convert; +#[allow(dead_code)] +pub mod file_io; +#[allow(dead_code)] +pub mod scoped_timer; +#[allow(dead_code)] +pub mod time; -pub use scoped_timer::ScopedTimer; +#[allow(dead_code)] +pub mod stream; + +#[cfg(test)] +mod convert_test; +#[cfg(test)] +mod file_io_test; +#[cfg(test)] +mod stream_test; +#[cfg(test)] +mod time_test; diff --git a/src/utils/stream.rs b/d-engine-core/src/utils/stream.rs similarity index 100% rename from src/utils/stream.rs rename to d-engine-core/src/utils/stream.rs diff --git a/src/utils/stream_test.rs b/d-engine-core/src/utils/stream_test.rs similarity index 100% rename from src/utils/stream_test.rs rename to d-engine-core/src/utils/stream_test.rs diff --git a/src/utils/time.rs b/d-engine-core/src/utils/time.rs similarity index 100% rename from src/utils/time.rs rename to d-engine-core/src/utils/time.rs diff --git a/src/utils/time_test.rs b/d-engine-core/src/utils/time_test.rs similarity index 100% rename from src/utils/time_test.rs rename to d-engine-core/src/utils/time_test.rs diff --git a/d-engine-proto/src/exts/common_ext.rs b/d-engine-proto/src/exts/common_ext.rs index f972fc33..3c7038ec 100644 --- a/d-engine-proto/src/exts/common_ext.rs +++ b/d-engine-proto/src/exts/common_ext.rs @@ -22,4 +22,31 @@ impl EntryPayload { })), } } + + pub fn is_config(&self) -> bool { + match self.payload { + Some(Payload::Command(_)) => false, + Some(Payload::Config(_)) => true, + Some(Payload::Noop(_)) => false, + None => false, + } + } + + pub fn is_command(&self) -> bool { + match self.payload { + Some(Payload::Command(_)) => true, + Some(Payload::Config(_)) => false, + Some(Payload::Noop(_)) => false, + None => false, + } + } + + pub fn is_noop(&self) -> bool { + match self.payload { + Some(Payload::Command(_)) => false, + Some(Payload::Config(_)) => false, + Some(Payload::Noop(_)) => true, + None => false, + } + } } diff --git a/d-engine-proto/src/exts/mod.rs b/d-engine-proto/src/exts/mod.rs index 805a8598..8a2a5ccc 100644 --- a/d-engine-proto/src/exts/mod.rs +++ b/d-engine-proto/src/exts/mod.rs @@ -2,8 +2,10 @@ mod client_ext; mod cluster_ext; mod common_ext; mod replication_ext; +mod storage_ext; pub use client_ext::*; pub use cluster_ext::*; pub use common_ext::*; pub use replication_ext::*; +pub use storage_ext::*; diff --git a/d-engine-proto/src/exts/storage_ext.rs b/d-engine-proto/src/exts/storage_ext.rs new file mode 100644 index 00000000..70340167 --- /dev/null +++ b/d-engine-proto/src/exts/storage_ext.rs @@ -0,0 +1,24 @@ +use std::error::Error; + +use bytes::Bytes; + +use crate::server::storage::SnapshotMetadata; + +impl SnapshotMetadata { + pub fn checksum_array(&self) -> Result<[u8; 32], Box> { + if self.checksum.len() == 32 { + let mut array = [0u8; 32]; + array.copy_from_slice(&self.checksum); + Ok(array) + } else { + Err(format!("Invalid checksum length: {}", self.checksum.len()).into()) + } + } + + pub fn set_checksum_array( + &mut self, + array: [u8; 32], + ) { + self.checksum = Bytes::copy_from_slice(&array); + } +} diff --git a/d-engine-runtime/Cargo.toml b/d-engine-runtime/Cargo.toml index e03bc13e..3c026534 100644 --- a/d-engine-runtime/Cargo.toml +++ b/d-engine-runtime/Cargo.toml @@ -5,12 +5,30 @@ edition = "2024" [dependencies] d-engine-proto = { workspace = true } +d-engine-core = { workspace = true, features = ["test-utils"] } + +tokio = { workspace = true } +tonic = { workspace = true } +bytes = { workspace = true } serde = { workspace = true } tracing = { workspace = true } tempfile = { workspace = true } +futures = { workspace = true } +prost = { workspace = true } + rocksdb = { version = "0.24.0", optional = true } config = { version = "0.14.0", default-features = false, features = ["toml"] } +arc-swap = "1.7.1" +dashmap = "5.5.3" +# use tokio_util::sync::CancellationToken; +tokio-util = "0.7.11" + +# certification +rcgen = { version = "0.13", features = ["pem"] } [dev-dependencies] serial_test = "3.2.0" temp-env = "0.3.6" + +tracing-test = "0.2" +tonic-health = "0.12.3" diff --git a/d-engine-runtime/src/lib.rs b/d-engine-runtime/src/lib.rs index 8b137891..609c8c29 100644 --- a/d-engine-runtime/src/lib.rs +++ b/d-engine-runtime/src/lib.rs @@ -1 +1,24 @@ +#![doc = include_str!("../../d-engine-docs/src/docs/overview.md")] +// #![warn(missing_docs)] + +pub mod node; +pub mod storage; + +#[doc(hidden)] +pub use node::*; + +mod membership; +mod network; + +pub(crate) use membership::*; +pub(crate) use network::*; +pub use storage::*; + +#[doc(hidden)] +pub mod utils; +#[doc(hidden)] +pub use utils::*; + +#[cfg(test)] +pub mod test_utils; diff --git a/src/membership/membership_guard.rs b/d-engine-runtime/src/membership/membership_guard.rs similarity index 95% rename from src/membership/membership_guard.rs rename to d-engine-runtime/src/membership/membership_guard.rs index 1cca0697..070ba8f4 100644 --- a/src/membership/membership_guard.rs +++ b/d-engine-runtime/src/membership/membership_guard.rs @@ -5,9 +5,9 @@ use arc_swap::ArcSwap; use tokio::sync::Mutex; use tracing::info; -use crate::proto::cluster::NodeMeta; -use crate::MembershipError; -use crate::Result; +use d_engine_core::MembershipError; +use d_engine_core::Result; +use d_engine_proto::server::cluster::NodeMeta; pub struct MembershipGuard { // Atomic state pointer for lock-free reads diff --git a/src/membership/membership_guard_test.rs b/d-engine-runtime/src/membership/membership_guard_test.rs similarity index 97% rename from src/membership/membership_guard_test.rs rename to d-engine-runtime/src/membership/membership_guard_test.rs index 3b2b3565..7a140203 100644 --- a/src/membership/membership_guard_test.rs +++ b/d-engine-runtime/src/membership/membership_guard_test.rs @@ -5,10 +5,10 @@ use tokio::sync::oneshot; use tracing_test::traced_test; use super::*; -use crate::proto::cluster::NodeMeta; -use crate::ConsensusError; -use crate::Error; -use crate::MembershipError; +use d_engine_core::ConsensusError; +use d_engine_core::Error; +use d_engine_core::MembershipError; +use d_engine_proto::server::cluster::NodeMeta; fn create_test_node(id: u32) -> NodeMeta { NodeMeta { diff --git a/src/membership/mod.rs b/d-engine-runtime/src/membership/mod.rs similarity index 100% rename from src/membership/mod.rs rename to d-engine-runtime/src/membership/mod.rs diff --git a/src/membership/raft_membership.rs b/d-engine-runtime/src/membership/raft_membership.rs similarity index 92% rename from src/membership/raft_membership.rs rename to d-engine-runtime/src/membership/raft_membership.rs index 20b6afa6..a877ab21 100644 --- a/src/membership/raft_membership.rs +++ b/d-engine-runtime/src/membership/raft_membership.rs @@ -15,9 +15,10 @@ use std::fmt::Debug; use std::marker::PhantomData; -use futures::stream::FuturesUnordered; +use d_engine_core::ensure_safe_join; use futures::FutureExt; use futures::StreamExt; +use futures::stream::FuturesUnordered; use tokio::task; use tonic::async_trait; use tonic::transport::Channel; @@ -28,29 +29,29 @@ use tracing::instrument; use tracing::warn; use super::MembershipGuard; -use crate::async_task::task_with_timeout_and_exponential_backoff; -use crate::net::address_str; -use crate::proto::cluster::ClusterConfChangeRequest; -use crate::proto::cluster::ClusterConfUpdateResponse; -use crate::proto::cluster::ClusterMembership; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::membership_change::Change; -use crate::proto::common::MembershipChange; -use crate::proto::common::NodeStatus; use crate::ConnectionCache; -use crate::ConnectionType; use crate::HealthChecker; use crate::HealthCheckerApis; use crate::HealthMonitor; -use crate::Membership; -use crate::MembershipError; use crate::RaftHealthMonitor; -use crate::RaftNodeConfig; -use crate::Result; -use crate::TypeConfig; -use crate::FOLLOWER; -use crate::LEADER; -use crate::LEARNER; +use crate::async_task::task_with_timeout_and_exponential_backoff; +use crate::net::address_str; +use d_engine_core::ConnectionType; +use d_engine_core::Membership; +use d_engine_core::MembershipError; +use d_engine_core::RaftNodeConfig; +use d_engine_core::Result; +use d_engine_core::TypeConfig; +use d_engine_proto::common::MembershipChange; +use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeRole::Leader; +use d_engine_proto::common::NodeRole::Learner; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::common::membership_change::Change; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::NodeMeta; pub struct RaftMembership where @@ -282,15 +283,15 @@ where leader_id: u32, ) -> Result<()> { self.reset_leader().await?; - self.update_node_role(leader_id, LEADER).await + self.update_node_role(leader_id, Leader.into()).await } async fn reset_leader(&self) -> Result<()> { self.membership .blocking_write(|guard| { for node in guard.nodes.values_mut() { - if node.role == LEADER { - node.role = FOLLOWER; + if node.role == Leader.into() { + node.role = Follower.into(); } } Ok(()) @@ -322,7 +323,7 @@ where async fn current_leader_id(&self) -> Option { self.membership .blocking_read(|guard| { - guard.nodes.values().find(|node| node.role == LEADER).map(|node| node.id) + guard.nodes.values().find(|node| node.role == Leader.into()).map(|node| node.id) }) .await } @@ -388,8 +389,8 @@ where } Some(Change::Promote(promote)) => { self.update_single_node(promote.node_id, |node| { - if node.role == LEARNER { - node.role = FOLLOWER; + if node.role == Learner.into() { + node.role = Follower.into(); Ok(()) } else { Err(MembershipError::InvalidPromotion { @@ -405,7 +406,7 @@ where self.update_multiple_nodes(&bp.node_ids, |node| { if NodeStatus::is_i32_promotable(node.status) { node.status = bp.new_status; - node.role = FOLLOWER; + node.role = Follower.into(); } Ok(()) }) @@ -493,7 +494,7 @@ where NodeMeta { id: node_id, address, - role: LEARNER, + role: Learner.into(), status: NodeStatus::Syncing as i32, }, ); @@ -673,7 +674,7 @@ where .nodes .get_mut(&promote.node_id) .map(|node| { - node.role = FOLLOWER; + node.role = Follower.into(); node.status = NodeStatus::Active as i32; Ok(()) }) @@ -685,7 +686,7 @@ where }) }) .await - // self.update_node_role(promote.node_id, FOLLOWER).await?; + // self.update_node_role(promote.node_id, Follower.into()).await?; // self.update_node_status(promote.node_id, NodeStatus::Active).await } Some(Change::BatchPromote(bp)) => { @@ -696,7 +697,7 @@ where MembershipError::NoMetadataFoundForNode { node_id: *node_id }, )?; - node.role = FOLLOWER; + node.role = Follower.into(); node.status = NodeStatus::try_from(bp.new_status) .unwrap_or(NodeStatus::Active) as i32; @@ -750,7 +751,7 @@ where role: i32, ) -> Result<()> { // New nodes must be learners - if role != LEARNER { + if role != Learner.into() { return Err(MembershipError::NotLearner.into()); } @@ -842,33 +843,3 @@ where .await } } - -#[instrument] -pub fn ensure_safe_join( - node_id: u32, - current_voters: usize, -) -> Result<()> { - // Total voters including leader = current_voters + 1 - let total_voters = current_voters + 1; - - // Always allow if cluster will have even number of voters - if (total_voters + 1).is_multiple_of(2) { - Ok(()) - } else { - // metrics::counter!("cluster.unsafe_join_attempts", 1); - metrics::counter!( - "cluster.unsafe_join_attempts", - &[("node_id", node_id.to_string())] - ) - .increment(1); - - warn!( - "Unsafe join attempt: current_voters={} (total_voters={})", - current_voters, total_voters - ); - Err( - MembershipError::JoinClusterError("Cluster must maintain odd number of voters".into()) - .into(), - ) - } -} diff --git a/src/membership/raft_membership_test.rs b/d-engine-runtime/src/membership/raft_membership_test.rs similarity index 89% rename from src/membership/raft_membership_test.rs rename to d-engine-runtime/src/membership/raft_membership_test.rs index 19b76ac4..71c86696 100644 --- a/src/membership/raft_membership_test.rs +++ b/d-engine-runtime/src/membership/raft_membership_test.rs @@ -2,67 +2,68 @@ use tokio::sync::oneshot; use tracing_test::traced_test; use super::RaftMembership; -use crate::ensure_safe_join; -use crate::proto::cluster::cluster_conf_update_response::ErrorCode; -use crate::proto::cluster::ClusterConfChangeRequest; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::membership_change::Change; -use crate::proto::common::AddNode; -use crate::proto::common::BatchRemove; -use crate::proto::common::MembershipChange; -use crate::proto::common::NodeStatus; -use crate::proto::common::PromoteLearner; -use crate::proto::common::RemoveNode; -use crate::test_utils::MockNode; -use crate::test_utils::MockRpcService; -use crate::test_utils::MockStorageEngine; -use crate::test_utils::MockTypeConfig; -use crate::ConnectionType; -use crate::ConsensusError; -use crate::Error; -use crate::Membership; -use crate::MembershipError; -use crate::MockStateMachine; -use crate::RaftNodeConfig; +use d_engine_core::ConnectionType; +use d_engine_core::ConsensusError; +use d_engine_core::Error; + use crate::RaftTypeConfig; -use crate::CANDIDATE; -use crate::FOLLOWER; -use crate::LEADER; -use crate::LEARNER; +use d_engine_core::Membership; +use d_engine_core::MembershipError; +use d_engine_core::MockStateMachine; +use d_engine_core::RaftNodeConfig; +use d_engine_core::ensure_safe_join; +use d_engine_core::test_utils::MockNode; +use d_engine_core::test_utils::MockRpcService; +use d_engine_core::test_utils::MockStorageEngine; +use d_engine_core::test_utils::MockTypeConfig; +use d_engine_proto::common::AddNode; +use d_engine_proto::common::BatchRemove; +use d_engine_proto::common::MembershipChange; +use d_engine_proto::common::NodeRole::Candidate; +use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeRole::Leader; +use d_engine_proto::common::NodeRole::Learner; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::common::PromoteLearner; +use d_engine_proto::common::RemoveNode; +use d_engine_proto::common::membership_change::Change; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::NodeMeta; +use d_engine_proto::server::cluster::cluster_conf_update_response::ErrorCode; // Helper function to create a test instance -pub fn create_test_membership( -) -> RaftMembership> { +pub fn create_test_membership() +-> RaftMembership> { // Prepare cluster membership let initial_cluster = vec![ NodeMeta { id: 1, address: "127.0.0.1:10000".to_string(), - role: LEADER, + role: Leader.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 2, address: "127.0.0.1:10000".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 3, address: "127.0.0.1:10000".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 4, address: "127.0.0.1:10000".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Joining.into(), }, NodeMeta { id: 5, address: "127.0.0.1:10000".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Joining.into(), }, ]; @@ -181,31 +182,31 @@ async fn test_replication_peers_case1() { NodeMeta { id: 1, address: "127.0.0.1:10000".to_string(), - role: LEADER, + role: Leader.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 2, address: "127.0.0.1:10000".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 3, address: "127.0.0.1:10000".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 4, address: "127.0.0.1:10000".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Syncing.into(), }, NodeMeta { id: 5, address: "127.0.0.1:10000".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Joining.into(), }, ]; @@ -236,31 +237,31 @@ async fn test_mark_leader_id_case1() { NodeMeta { id: old_leader_id, address: "127.0.0.1:10000".to_string(), - role: LEADER, + role: Leader.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 3, address: "127.0.0.1:10000".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 4, address: "127.0.0.1:10000".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 5, address: "127.0.0.1:10000".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 6, address: "127.0.0.1:10000".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Active.into(), }, ]; @@ -292,31 +293,31 @@ async fn test_mark_leader_id_case2() { NodeMeta { id: 1, address: "127.0.0.1:10000".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 3, address: "127.0.0.1:10000".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 4, address: "127.0.0.1:10000".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 5, address: "127.0.0.1:10000".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 6, address: "127.0.0.1:10000".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Active.into(), }, ]; @@ -338,31 +339,31 @@ async fn test_retrieve_cluster_membership_config() { NodeMeta { id: 1, address: "127.0.0.1:10000".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 3, address: "127.0.0.1:10000".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 4, address: "127.0.0.1:10000".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 5, address: "127.0.0.1:10000".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 6, address: "127.0.0.1:10000".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Active.into(), }, ]; @@ -374,7 +375,7 @@ async fn test_retrieve_cluster_membership_config() { let r = membership.retrieve_cluster_membership_config().await; assert_eq!(r.nodes.len(), 5); - assert!(!r.nodes.iter().any(|n| n.role == LEADER)); + assert!(!r.nodes.iter().any(|n| n.role == Leader.into())); } #[tokio::test] @@ -394,16 +395,18 @@ async fn test_update_cluster_conf_from_leader_case1() { }), }; - assert!(membership - .update_cluster_conf_from_leader( - 1, // my_id - 1, // my_current_term - 0, // current_conf_version - Some(2), // current_leader_id - &req, - ) - .await - .is_ok()); + assert!( + membership + .update_cluster_conf_from_leader( + 1, // my_id + 1, // my_current_term + 0, // current_conf_version + Some(2), // current_leader_id + &req, + ) + .await + .is_ok() + ); assert!(membership.contains_node(3).await); assert_eq!(membership.get_cluster_conf_version().await, 0); // Version from request } @@ -417,7 +420,7 @@ async fn test_update_cluster_conf_from_leader_case2() { vec![NodeMeta { id: 3, address: "127.0.0.1:8080".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }], RaftNodeConfig::default(), @@ -431,16 +434,18 @@ async fn test_update_cluster_conf_from_leader_case2() { }), }; - assert!(membership - .update_cluster_conf_from_leader( - 1, // my_id - 1, // my_current_term - 0, // current_conf_version - Some(2), // current_leader_id - &req, - ) - .await - .is_ok()); + assert!( + membership + .update_cluster_conf_from_leader( + 1, // my_id + 1, // my_current_term + 0, // current_conf_version + Some(2), // current_leader_id + &req, + ) + .await + .is_ok() + ); assert!(!membership.contains_node(3).await); assert_eq!(membership.get_cluster_conf_version().await, 1); } @@ -454,7 +459,7 @@ async fn test_update_cluster_conf_from_leader_case3() { vec![NodeMeta { id: 3, address: "127.0.0.1:8080".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Active.into(), }], RaftNodeConfig::default(), @@ -483,7 +488,10 @@ async fn test_update_cluster_conf_from_leader_case3() { .expect("should succeed"); assert!(response.success); - assert_eq!(membership.get_role_by_node_id(3).await.unwrap(), FOLLOWER); + assert_eq!( + membership.get_role_by_node_id(3).await.unwrap(), + Follower.into() + ); assert_eq!(membership.get_cluster_conf_version().await, 1); } @@ -496,7 +504,7 @@ async fn test_update_cluster_conf_from_leader_case4_conf_invalid_promotion() { vec![NodeMeta { id: 3, address: "127.0.0.1:8080".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }], RaftNodeConfig::default(), @@ -606,25 +614,25 @@ async fn test_batch_remove_nodes() { NodeMeta { id: 2, address: "127.0.0.1:10001".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active as i32, }, NodeMeta { id: 3, address: "127.0.0.1:10002".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active as i32, }, NodeMeta { id: 4, address: "127.0.0.1:10003".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Syncing as i32, }, NodeMeta { id: 5, address: "127.0.0.1:10004".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Joining as i32, }, ]; @@ -674,13 +682,13 @@ async fn test_batch_remove_leader_protection() { NodeMeta { id: 1, address: "127.0.0.1:10000".to_string(), - role: LEADER, + role: Leader.into(), status: NodeStatus::Active as i32, }, NodeMeta { id: 2, address: "127.0.0.1:10001".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active as i32, }, ]; @@ -721,19 +729,19 @@ async fn test_apply_batch_remove_config_change() { NodeMeta { id: 2, address: "127.0.0.1:10001".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active as i32, }, NodeMeta { id: 3, address: "127.0.0.1:10002".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Syncing as i32, }, NodeMeta { id: 4, address: "127.0.0.1:10003".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active as i32, }, ], @@ -768,19 +776,19 @@ async fn test_update_cluster_conf_from_leader_case7_batch_remove() { NodeMeta { id: 2, address: "127.0.0.1:10001".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active as i32, }, NodeMeta { id: 3, address: "127.0.0.1:10002".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Syncing as i32, }, NodeMeta { id: 4, address: "127.0.0.1:10003".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active as i32, }, ]; @@ -852,31 +860,31 @@ async fn test_get_peers_id_with_condition() { NodeMeta { id: 1, address: "127.0.0.1:10000".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 3, address: "127.0.0.1:10000".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 4, address: "127.0.0.1:10000".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 5, address: "127.0.0.1:10000".to_string(), - role: CANDIDATE, + role: Candidate.into(), status: NodeStatus::Active.into(), }, NodeMeta { id: 6, address: "127.0.0.1:10000".to_string(), - role: LEADER, + role: Leader.into(), status: NodeStatus::Active.into(), }, ]; @@ -888,7 +896,7 @@ async fn test_get_peers_id_with_condition() { // Test 1: Filter followers and candidates let mut result = membership - .get_peers_id_with_condition(|role| role == FOLLOWER || role == CANDIDATE) + .get_peers_id_with_condition(|role| role == Follower.into() || role == Candidate.into()) .await; result.sort_unstable(); let mut expect = vec![1, 3, 5]; @@ -896,7 +904,7 @@ async fn test_get_peers_id_with_condition() { assert_eq!(result, expect, "Should return follower and candidate IDs"); // Test 2: Filter leaders only - let result = membership.get_peers_id_with_condition(|role| role == LEADER).await; + let result = membership.get_peers_id_with_condition(|role| role == Leader.into()).await; assert_eq!(result, vec![6], "Should return leader ID only"); // Test 3: Empty result case @@ -922,10 +930,10 @@ mod check_cluster_is_ready_test { use tokio::sync::oneshot; use super::*; + use crate::MockStateMachine; use crate::test_utils::MockNode; use crate::test_utils::MockRpcService; use crate::test_utils::MockStorageEngine; - use crate::MockStateMachine; /// Case 1: Test all peers are healthy #[tokio::test] @@ -941,7 +949,7 @@ mod check_cluster_is_ready_test { .map(|id| NodeMeta { id: *id, address: "127.0.0.1:0".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }) .collect(), @@ -979,7 +987,7 @@ mod check_cluster_is_ready_test { vec![NodeMeta { id: 2, address: "127.0.0.1:9999".to_string(), // Invalid port - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }], config, @@ -1003,7 +1011,7 @@ mod check_cluster_is_ready_test { vec![NodeMeta { id: 4, address: format!("127.0.0.1:{port}",), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }], config, @@ -1032,7 +1040,7 @@ mod check_cluster_is_ready_test { .map(|id| NodeMeta { id: *id, address: "127.0.0.1:0".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }) .collect(), @@ -1077,7 +1085,7 @@ mod add_learner_test { assert_eq!(replication_members.len(), 1); assert_eq!(replication_members[0].id, 2); assert_eq!(replication_members[0].address, "127.0.0.1:1234"); - assert_eq!(replication_members[0].role, LEARNER); + assert_eq!(replication_members[0].role, Learner.into()); assert_eq!(replication_members[0].status, NodeStatus::Syncing as i32); } @@ -1088,7 +1096,7 @@ mod add_learner_test { vec![NodeMeta { id: 1, address: "127.0.0.1:0".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }], RaftNodeConfig::default(), @@ -1101,7 +1109,7 @@ mod add_learner_test { assert_eq!(replication_members.len(), 1); assert_eq!(replication_members[0].id, 1); assert_eq!(replication_members[0].address, "127.0.0.1:0"); - assert_eq!(replication_members[0].role, FOLLOWER); + assert_eq!(replication_members[0].role, Follower.into()); assert_eq!(replication_members[0].status, NodeStatus::Active as i32); } } @@ -1157,9 +1165,9 @@ mod pre_warm_connections_tests { use super::*; use crate::net::address_str; - use crate::proto::cluster::NodeMeta; - use crate::proto::common::NodeStatus; use crate::test_utils; + use d_engine_proto::common::NodeStatus; + use d_engine_proto::server::cluster::NodeMeta; #[derive(Clone, Copy)] pub enum AddressType { @@ -1190,7 +1198,7 @@ mod pre_warm_connections_tests { cluster.push(NodeMeta { id, address, - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active.into(), }); diff --git a/src/network/connection_cache.rs b/d-engine-runtime/src/network/connection_cache.rs similarity index 98% rename from src/network/connection_cache.rs rename to d-engine-runtime/src/network/connection_cache.rs index ec5792b8..5a64d18b 100644 --- a/src/network/connection_cache.rs +++ b/d-engine-runtime/src/network/connection_cache.rs @@ -9,8 +9,8 @@ use tracing::trace; use crate::ConnectionType; use crate::NetworkConfig; -use crate::NetworkError; -use crate::Result; +use d_engine_core::NetworkError; +use d_engine_core::Result; /// Cached gRPC channel with metadata #[derive(Clone)] diff --git a/src/network/connection_cache_test.rs b/d-engine-runtime/src/network/connection_cache_test.rs similarity index 100% rename from src/network/connection_cache_test.rs rename to d-engine-runtime/src/network/connection_cache_test.rs diff --git a/src/network/grpc/grpc_raft_service.rs b/d-engine-runtime/src/network/grpc/grpc_raft_service.rs similarity index 91% rename from src/network/grpc/grpc_raft_service.rs rename to d-engine-runtime/src/network/grpc/grpc_raft_service.rs index ee3a1892..15189a0d 100644 --- a/src/network/grpc/grpc_raft_service.rs +++ b/d-engine-runtime/src/network/grpc/grpc_raft_service.rs @@ -16,37 +16,37 @@ use tracing::debug; use tracing::error; use tracing::warn; -use crate::proto::client::raft_client_service_server::RaftClientService; -use crate::proto::client::ClientReadRequest; -use crate::proto::client::ClientResponse; -use crate::proto::client::ClientWriteRequest; -use crate::proto::cluster::cluster_management_service_server::ClusterManagementService; -use crate::proto::cluster::ClusterConfChangeRequest; -use crate::proto::cluster::ClusterConfUpdateResponse; -use crate::proto::cluster::ClusterMembership; -use crate::proto::cluster::JoinRequest; -use crate::proto::cluster::JoinResponse; -use crate::proto::cluster::LeaderDiscoveryRequest; -use crate::proto::cluster::LeaderDiscoveryResponse; -use crate::proto::cluster::MetadataRequest; -use crate::proto::election::raft_election_service_server::RaftElectionService; -use crate::proto::election::VoteRequest; -use crate::proto::election::VoteResponse; -use crate::proto::replication::raft_replication_service_server::RaftReplicationService; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::replication::AppendEntriesResponse; -use crate::proto::storage::snapshot_service_server::SnapshotService; -use crate::proto::storage::PurgeLogRequest; -use crate::proto::storage::PurgeLogResponse; -use crate::proto::storage::SnapshotAck; -use crate::proto::storage::SnapshotChunk; -use crate::proto::storage::SnapshotResponse; use crate::MaybeCloneOneshot; use crate::Node; use crate::RaftEvent; use crate::RaftOneshot; use crate::StreamResponseSender; -use crate::TypeConfig; +use d_engine_core::TypeConfig; +use d_engine_proto::client::ClientReadRequest; +use d_engine_proto::client::ClientResponse; +use d_engine_proto::client::ClientWriteRequest; +use d_engine_proto::client::raft_client_service_server::RaftClientService; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::JoinRequest; +use d_engine_proto::server::cluster::JoinResponse; +use d_engine_proto::server::cluster::LeaderDiscoveryRequest; +use d_engine_proto::server::cluster::LeaderDiscoveryResponse; +use d_engine_proto::server::cluster::MetadataRequest; +use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementService; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::election::raft_election_service_server::RaftElectionService; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationService; +use d_engine_proto::storage::PurgeLogRequest; +use d_engine_proto::storage::PurgeLogResponse; +use d_engine_proto::storage::SnapshotAck; +use d_engine_proto::storage::SnapshotChunk; +use d_engine_proto::storage::SnapshotResponse; +use d_engine_proto::storage::snapshot_service_server::SnapshotService; #[tonic::async_trait] impl RaftElectionService for Node diff --git a/src/network/grpc/grpc_raft_service_test.rs b/d-engine-runtime/src/network/grpc/grpc_raft_service_test.rs similarity index 79% rename from src/network/grpc/grpc_raft_service_test.rs rename to d-engine-runtime/src/network/grpc/grpc_raft_service_test.rs index facac630..5afaffbc 100644 --- a/src/network/grpc/grpc_raft_service_test.rs +++ b/d-engine-runtime/src/network/grpc/grpc_raft_service_test.rs @@ -8,33 +8,33 @@ use tonic::Code; use tonic::Request; use tracing_test::traced_test; -use crate::convert::safe_kv_bytes; -use crate::proto::client::raft_client_service_server::RaftClientService; -use crate::proto::client::ClientReadRequest; -use crate::proto::client::ClientWriteRequest; -use crate::proto::client::ReadConsistencyPolicy; -use crate::proto::client::WriteCommand; -use crate::proto::cluster::cluster_management_service_server::ClusterManagementService; -use crate::proto::cluster::ClusterConfChangeRequest; -use crate::proto::cluster::ClusterConfUpdateResponse; -use crate::proto::cluster::ClusterMembership; -use crate::proto::cluster::MetadataRequest; -use crate::proto::common::LogId; -use crate::proto::election::raft_election_service_server::RaftElectionService; -use crate::proto::election::VoteRequest; -use crate::proto::replication::raft_replication_service_server::RaftReplicationService; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::replication::AppendEntriesResponse; -use crate::test_utils::mock_node; -use crate::test_utils::MockBuilder; -use crate::test_utils::MockTypeConfig; use crate::AppendResponseWithUpdates; use crate::AppendResults; use crate::MockElectionCore; -use crate::MockMembership; use crate::MockReplicationCore; use crate::RaftNodeConfig; use crate::StateUpdate; +use crate::convert::safe_kv_bytes; +use crate::test_utils::MockBuilder; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::mock_node; +use d_engine_core::MockMembership; +use d_engine_proto::client::ClientReadRequest; +use d_engine_proto::client::ClientWriteRequest; +use d_engine_proto::client::ReadConsistencyPolicy; +use d_engine_proto::client::WriteCommand; +use d_engine_proto::client::raft_client_service_server::RaftClientService; +use d_engine_proto::common::LogId; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::MetadataRequest; +use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementService; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::election::raft_election_service_server::RaftElectionService; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationService; /// # Case: Test RPC services timeout #[tokio::test] @@ -44,19 +44,20 @@ async fn test_handle_service_timeout() { let node = mock_node("/tmp/test_handle_service_timeout", graceful_rx, None); // Vote request - assert!(node - .request_vote(Request::new(VoteRequest { + assert!( + node.request_vote(Request::new(VoteRequest { term: 1, candidate_id: 1, last_log_index: 0, last_log_term: 0, })) .await - .is_err()); + .is_err() + ); // Append Entries request - assert!(node - .append_entries(Request::new(AppendEntriesRequest { + assert!( + node.append_entries(Request::new(AppendEntriesRequest { term: 1, leader_id: 1, prev_log_index: 0, @@ -65,40 +66,44 @@ async fn test_handle_service_timeout() { leader_commit_index: 1 })) .await - .is_err()); + .is_err() + ); // Update cluster conf request - assert!(node - .update_cluster_conf(Request::new(ClusterConfChangeRequest { + assert!( + node.update_cluster_conf(Request::new(ClusterConfChangeRequest { id: 1, term: 1, version: 1, change: None })) .await - .is_err()); + .is_err() + ); // Client Propose request - assert!(node - .handle_client_write(Request::new(ClientWriteRequest { + assert!( + node.handle_client_write(Request::new(ClientWriteRequest { commands: vec![], client_id: 1, })) .await - .is_err()); + .is_err() + ); // Metadata request assert!(node.get_cluster_metadata(Request::new(MetadataRequest {})).await.is_err()); // Client read request - assert!(node - .handle_client_read(Request::new(ClientReadRequest { + assert!( + node.handle_client_read(Request::new(ClientReadRequest { client_id: 1, consistency_policy: Some(ReadConsistencyPolicy::LinearizableRead as i32), keys: vec![] })) .await - .is_err()); + .is_err() + ); } /// # Case: Test server is not ready @@ -252,18 +257,19 @@ async fn test_handle_rpc_services_successfully() { tokio::time::sleep(Duration::from_millis(2)).await; let service_handler = tokio::spawn(async move { - assert!(node - .request_vote(Request::new(VoteRequest { + assert!( + node.request_vote(Request::new(VoteRequest { term: 1, candidate_id: 1, last_log_index: 0, last_log_term: 0, })) .await - .is_ok()); + .is_ok() + ); - assert!(node - .append_entries(Request::new(AppendEntriesRequest { + assert!( + node.append_entries(Request::new(AppendEntriesRequest { term: 1, leader_id: 1, prev_log_index: 0, @@ -272,36 +278,40 @@ async fn test_handle_rpc_services_successfully() { leader_commit_index: 1, })) .await - .is_ok()); + .is_ok() + ); - assert!(node - .update_cluster_conf(Request::new(ClusterConfChangeRequest { + assert!( + node.update_cluster_conf(Request::new(ClusterConfChangeRequest { id: 1, term: 1, version: 1, change: None })) .await - .is_ok()); + .is_ok() + ); - assert!(node - .handle_client_write(Request::new(ClientWriteRequest { + assert!( + node.handle_client_write(Request::new(ClientWriteRequest { client_id: 1, commands: vec![WriteCommand::delete(safe_kv_bytes(1))], })) .await - .is_ok()); + .is_ok() + ); assert!(node.get_cluster_metadata(Request::new(MetadataRequest {})).await.is_ok()); - assert!(node - .handle_client_read(Request::new(ClientReadRequest { + assert!( + node.handle_client_read(Request::new(ClientReadRequest { client_id: 1, consistency_policy: Some(ReadConsistencyPolicy::EventualConsistency as i32), keys: vec![], })) .await - .is_ok()); + .is_ok() + ); }); tokio::time::sleep(Duration::from_millis(100)).await; diff --git a/src/network/grpc/grpc_transport.rs b/d-engine-runtime/src/network/grpc/grpc_transport.rs similarity index 95% rename from src/network/grpc/grpc_transport.rs rename to d-engine-runtime/src/network/grpc/grpc_transport.rs index 1964d931..9e509349 100644 --- a/src/network/grpc/grpc_transport.rs +++ b/d-engine-runtime/src/network/grpc/grpc_transport.rs @@ -6,9 +6,9 @@ use std::marker::PhantomData; use std::sync::Arc; use dashmap::DashMap; -use futures::stream::FuturesUnordered; use futures::FutureExt; use futures::StreamExt; +use futures::stream::FuturesUnordered; use tokio::sync::mpsc; use tokio::sync::oneshot; use tokio::task; @@ -22,24 +22,6 @@ use tracing::error; use tracing::info; use tracing::warn; -use crate::alias::MOF; -use crate::grpc_task_with_timeout_and_exponential_backoff; -use crate::proto::cluster::cluster_management_service_client::ClusterManagementServiceClient; -use crate::proto::cluster::ClusterConfChangeRequest; -use crate::proto::cluster::JoinResponse; -use crate::proto::cluster::LeaderDiscoveryRequest; -use crate::proto::cluster::LeaderDiscoveryResponse; -use crate::proto::election::raft_election_service_client::RaftElectionServiceClient; -use crate::proto::election::VoteRequest; -use crate::proto::replication::raft_replication_service_client::RaftReplicationServiceClient; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::replication::AppendEntriesResponse; -use crate::proto::storage::snapshot_service_client::SnapshotServiceClient; -use crate::proto::storage::PurgeLogRequest; -use crate::proto::storage::PurgeLogResponse; -use crate::proto::storage::SnapshotAck; -use crate::proto::storage::SnapshotChunk; -use crate::scoped_timer::ScopedTimer; use crate::AppendResult; use crate::BackoffPolicy; use crate::ClusterUpdateResult; @@ -47,12 +29,30 @@ use crate::ConnectionType; use crate::Error; use crate::InstallSnapshotBackoffPolicy; use crate::Membership; -use crate::NetworkError; -use crate::Result; use crate::RetryPolicies; -use crate::Transport; -use crate::TypeConfig; use crate::VoteResult; +use crate::alias::MOF; +use crate::grpc_task_with_timeout_and_exponential_backoff; +use crate::scoped_timer::ScopedTimer; +use d_engine_core::NetworkError; +use d_engine_core::Result; +use d_engine_core::Transport; +use d_engine_core::TypeConfig; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::JoinResponse; +use d_engine_proto::server::cluster::LeaderDiscoveryRequest; +use d_engine_proto::server::cluster::LeaderDiscoveryResponse; +use d_engine_proto::server::cluster::cluster_management_service_client::ClusterManagementServiceClient; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::election::raft_election_service_client::RaftElectionServiceClient; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::replication::raft_replication_service_client::RaftReplicationServiceClient; +use d_engine_proto::storage::PurgeLogRequest; +use d_engine_proto::storage::PurgeLogResponse; +use d_engine_proto::storage::SnapshotAck; +use d_engine_proto::storage::SnapshotChunk; +use d_engine_proto::storage::snapshot_service_client::SnapshotServiceClient; pub struct PeerAppender { pub(crate) sender: mpsc::Sender, diff --git a/src/network/grpc/grpc_transport_test.rs b/d-engine-runtime/src/network/grpc/grpc_transport_test.rs similarity index 92% rename from src/network/grpc/grpc_transport_test.rs rename to d-engine-runtime/src/network/grpc/grpc_transport_test.rs index ab98d16c..84961d16 100644 --- a/src/network/grpc/grpc_transport_test.rs +++ b/d-engine-runtime/src/network/grpc/grpc_transport_test.rs @@ -1,46 +1,46 @@ use std::collections::HashMap; use bytes::Bytes; +use futures::StreamExt; use futures::stream; use futures::stream::BoxStream; -use futures::StreamExt; use tokio::sync::oneshot; +use tonic::Status; use tonic::transport::Channel; use tonic::transport::Endpoint; -use tonic::Status; use tracing_test::traced_test; use super::*; -use crate::grpc::grpc_transport::GrpcTransport; -use crate::proto::cluster::ClusterConfChangeRequest; -use crate::proto::cluster::ClusterMembership; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::LogId; -use crate::proto::common::NodeStatus; -use crate::proto::election::VoteRequest; -use crate::proto::election::VoteResponse; -use crate::proto::replication::AppendEntriesRequest; -use crate::proto::replication::AppendEntriesResponse; -use crate::proto::storage::PurgeLogRequest; -use crate::proto::storage::PurgeLogResponse; -use crate::proto::storage::SnapshotChunk; -use crate::test_utils::crate_test_snapshot_stream; -use crate::test_utils::create_test_chunk; -use crate::test_utils::node_config; -use crate::test_utils::MockNode; -use crate::test_utils::MockTypeConfig; use crate::ConnectionType; use crate::Error; -use crate::MockMembership; -use crate::NetworkError; use crate::RaftNodeConfig; use crate::RetryPolicies; use crate::SystemError; -use crate::Transport; -use crate::CANDIDATE; -use crate::FOLLOWER; -use crate::LEADER; -use crate::LEARNER; +use crate::grpc::grpc_transport::GrpcTransport; +use crate::test_utils::MockNode; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::crate_test_snapshot_stream; +use crate::test_utils::create_test_chunk; +use crate::test_utils::node_config; +use d_engine_core::MockMembership; +use d_engine_core::NetworkError; +use d_engine_core::Transport; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Candidate; +use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeRole::Leader; +use d_engine_proto::common::NodeRole::Learner; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::ClusterMembership; +use d_engine_proto::server::cluster::NodeMeta; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::replication::AppendEntriesRequest; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::storage::PurgeLogRequest; +use d_engine_proto::storage::PurgeLogResponse; +use d_engine_proto::storage::SnapshotChunk; fn mock_membership( peers: Vec<(u32, i32)>, //(node_id, role_i32) @@ -128,7 +128,7 @@ async fn test_send_cluster_update_case2() { let mut channels = HashMap::new(); channels.insert((my_id, ConnectionType::Control), channel.clone()); - let membership = mock_membership(vec![(my_id, FOLLOWER)], channels); + let membership = mock_membership(vec![(my_id, Follower.into())], channels); let client: GrpcTransport = GrpcTransport::new(my_id); match client.send_cluster_update(request, &node_config.retry, membership).await { Ok(res) => { @@ -168,7 +168,10 @@ async fn test_send_cluster_update_case3() { let mut channels = HashMap::new(); channels.insert((peer1_id, ConnectionType::Control), channel.clone()); channels.insert((peer2_id, ConnectionType::Control), channel.clone()); - let membership = mock_membership(vec![(peer1_id, FOLLOWER), (peer2_id, CANDIDATE)], channels); + let membership = mock_membership( + vec![(peer1_id, Follower.into()), (peer2_id, Candidate.into())], + channels, + ); let client: GrpcTransport = GrpcTransport::new(my_id); match client.send_cluster_update(request, &node_config.retry, membership).await { @@ -217,7 +220,10 @@ async fn test_send_cluster_update_case4() { let mut channels = HashMap::new(); channels.insert((peer1_id, ConnectionType::Control), channel.clone()); channels.insert((peer2_id, ConnectionType::Control), channel.clone()); - let membership = mock_membership(vec![(peer1_id, FOLLOWER), (peer2_id, CANDIDATE)], channels); + let membership = mock_membership( + vec![(peer1_id, Follower.into()), (peer2_id, Candidate.into())], + channels, + ); let client: GrpcTransport = GrpcTransport::new(my_id); match client.send_cluster_update(request, &node_config.retry, membership).await { @@ -289,7 +295,7 @@ async fn test_send_append_requests_case2() { let mut channels = HashMap::new(); channels.insert((leader_id, ConnectionType::Control), channel.clone()); - let membership = mock_membership(vec![(leader_id, LEADER)], channels); + let membership = mock_membership(vec![(leader_id, Leader.into())], channels); let node_config = node_config("/tmp/test_send_append_requests_case2"); @@ -372,7 +378,7 @@ async fn test_send_append_requests_case3_1() { let mut channels = HashMap::new(); channels.insert((peer_2_id, ConnectionType::Data), channel2.clone()); channels.insert((peer_3_id, ConnectionType::Data), channel3.clone()); - let membership = mock_membership(vec![(leader_id, LEADER)], channels); + let membership = mock_membership(vec![(leader_id, Leader.into())], channels); let node_config = RaftNodeConfig::new().expect("Should succeed to init RaftNodeConfig."); @@ -449,7 +455,7 @@ async fn test_send_append_requests_case3_2() { let mut channels = HashMap::new(); channels.insert((peer_2_id, ConnectionType::Data), channel2.clone()); channels.insert((peer_3_id, ConnectionType::Data), channel3.clone()); - let membership = mock_membership(vec![(leader_id, LEADER)], channels); + let membership = mock_membership(vec![(leader_id, Leader.into())], channels); let node_config = RaftNodeConfig::new().expect("Should succeed to init RaftNodeConfig."); @@ -532,7 +538,7 @@ async fn test_send_vote_requests_case2() { .expect("should succeed"); let mut channels = HashMap::new(); channels.insert((my_id, ConnectionType::Control), channel.clone()); - let membership = mock_membership(vec![(my_id, FOLLOWER)], channels); + let membership = mock_membership(vec![(my_id, Follower.into())], channels); let client: GrpcTransport = GrpcTransport::new(my_id); match client.send_vote_requests(request, &node_config.retry, membership).await { Ok(res) => { @@ -581,7 +587,10 @@ async fn test_send_vote_requests_case3() { let mut channels = HashMap::new(); channels.insert((peer1_id, ConnectionType::Control), channel.clone()); channels.insert((peer2_id, ConnectionType::Control), channel.clone()); - let membership = mock_membership(vec![(peer1_id, FOLLOWER), (peer2_id, CANDIDATE)], channels); + let membership = mock_membership( + vec![(peer1_id, Follower.into()), (peer2_id, Candidate.into())], + channels, + ); let client: GrpcTransport = GrpcTransport::new(my_id); match client.send_vote_requests(request, &node_config.retry, membership).await { Ok(res) => { @@ -630,7 +639,10 @@ async fn test_send_vote_requests_case4_1() { let mut channels = HashMap::new(); channels.insert((peer1_id, ConnectionType::Control), channel.clone()); channels.insert((peer2_id, ConnectionType::Control), channel.clone()); - let membership = mock_membership(vec![(peer1_id, FOLLOWER), (peer2_id, CANDIDATE)], channels); + let membership = mock_membership( + vec![(peer1_id, Follower.into()), (peer2_id, Candidate.into())], + channels, + ); let client: GrpcTransport = GrpcTransport::new(my_id); match client.send_vote_requests(request, &node_config.retry, membership).await { @@ -681,7 +693,10 @@ async fn test_send_vote_requests_case4_2() { let mut channels = HashMap::new(); channels.insert((peer1_id, ConnectionType::Control), channel.clone()); channels.insert((peer2_id, ConnectionType::Control), channel.clone()); - let membership = mock_membership(vec![(peer1_id, FOLLOWER), (peer2_id, CANDIDATE)], channels); + let membership = mock_membership( + vec![(peer1_id, Follower.into()), (peer2_id, Candidate.into())], + channels, + ); let client: GrpcTransport = GrpcTransport::new(my_id); assert!(client.send_vote_requests(request, &node_config.retry, membership).await.is_ok()); } @@ -727,7 +742,10 @@ async fn test_send_vote_requests_case4_3() { let mut channels = HashMap::new(); channels.insert((peer1_id, ConnectionType::Control), channel.clone()); channels.insert((peer2_id, ConnectionType::Control), channel.clone()); - let membership = mock_membership(vec![(peer1_id, FOLLOWER), (peer2_id, CANDIDATE)], channels); + let membership = mock_membership( + vec![(peer1_id, Follower.into()), (peer2_id, Candidate.into())], + channels, + ); let client: GrpcTransport = GrpcTransport::new(my_id); assert!(client.send_vote_requests(request, &node_config.retry, membership).await.is_ok()); } @@ -778,7 +796,10 @@ async fn test_send_vote_requests_case5() { let mut channels = HashMap::new(); channels.insert((peer1_id, ConnectionType::Control), channel1.clone()); channels.insert((peer2_id, ConnectionType::Control), channel2.clone()); - let membership = mock_membership(vec![(peer1_id, FOLLOWER), (peer2_id, CANDIDATE)], channels); + let membership = mock_membership( + vec![(peer1_id, Follower.into()), (peer2_id, Candidate.into())], + channels, + ); let client: GrpcTransport = GrpcTransport::new(my_id); match client.send_vote_requests(request, &node_config.retry, membership).await { Ok(res) => { @@ -850,7 +871,7 @@ async fn test_purge_requests_case2_self_reference() { let mut channels = HashMap::new(); channels.insert((my_id, ConnectionType::Data), channel.clone()); - let membership = mock_membership(vec![(my_id, FOLLOWER)], channels); + let membership = mock_membership(vec![(my_id, Follower.into())], channels); let client: GrpcTransport = GrpcTransport::new(my_id); let result = client.send_purge_requests(req, &node_config.retry, membership).await; @@ -897,7 +918,7 @@ async fn test_purge_requests_case3_duplicate_peers() { let mut channels = HashMap::new(); channels.insert((2, ConnectionType::Data), channel.clone()); channels.insert((3, ConnectionType::Data), channel.clone()); - let membership = mock_membership(vec![(2, LEARNER), (3, FOLLOWER)], channels); + let membership = mock_membership(vec![(2, Learner.into()), (3, Follower.into())], channels); let client: GrpcTransport = GrpcTransport::new(my_id); let result = client.send_purge_requests(req, &node_config.retry, membership).await; @@ -960,7 +981,7 @@ async fn test_purge_requests_case4_mixed_responses() { let mut channels = HashMap::new(); channels.insert((2, ConnectionType::Data), success_channel.clone()); channels.insert((3, ConnectionType::Data), failure_channel.clone()); - let membership = mock_membership(vec![(2, FOLLOWER), (3, LEARNER)], channels); + let membership = mock_membership(vec![(2, Follower.into()), (3, Learner.into())], channels); let client: GrpcTransport = GrpcTransport::new(my_id); let result = client.send_purge_requests(req, &node_config.retry, membership).await; @@ -1012,7 +1033,7 @@ async fn test_purge_requests_case5_full_success() { let mut channels = HashMap::new(); channels.insert((2, ConnectionType::Data), channel.clone()); channels.insert((3, ConnectionType::Data), channel.clone()); - let membership = mock_membership(vec![(2, FOLLOWER), (3, LEARNER)], channels); + let membership = mock_membership(vec![(2, Follower.into()), (3, Learner.into())], channels); let client: GrpcTransport = GrpcTransport::new(my_id); let result = client.send_purge_requests(req, &node_config.retry, membership).await; diff --git a/src/network/grpc/mod.rs b/d-engine-runtime/src/network/grpc/mod.rs similarity index 91% rename from src/network/grpc/mod.rs rename to d-engine-runtime/src/network/grpc/mod.rs index 7f023378..7a3717b4 100644 --- a/src/network/grpc/mod.rs +++ b/d-engine-runtime/src/network/grpc/mod.rs @@ -7,14 +7,9 @@ // Protobuf GRPC service introduction // ----------------------------------------------------------------------------- -mod backgroup_snapshot_transfer; mod grpc_raft_service; pub(crate) mod grpc_transport; -pub(crate) use backgroup_snapshot_transfer::*; - -#[cfg(test)] -mod backgroup_snapshot_transfer_test; #[cfg(test)] mod grpc_raft_service_test; @@ -29,8 +24,8 @@ use std::sync::Arc; use std::time::Duration; use futures::FutureExt; -use rcgen::generate_simple_self_signed; use rcgen::CertifiedKey; +use rcgen::generate_simple_self_signed; use tokio::sync::watch; use tonic::codec::CompressionEncoding; use tonic::transport::Certificate; @@ -42,17 +37,17 @@ use tracing::error; use tracing::info; use tracing::warn; -use crate::proto::client::raft_client_service_server::RaftClientServiceServer; -use crate::proto::cluster::cluster_management_service_server::ClusterManagementServiceServer; -use crate::proto::election::raft_election_service_server::RaftElectionServiceServer; -use crate::proto::replication::raft_replication_service_server::RaftReplicationServiceServer; -use crate::proto::storage::snapshot_service_server::SnapshotServiceServer; use crate::Node; use crate::RaftNodeConfig; -use crate::Result; use crate::SystemError; use crate::TlsConfig; -use crate::TypeConfig; +use d_engine_core::Result; +use d_engine_core::TypeConfig; +use d_engine_proto::client::raft_client_service_server::RaftClientServiceServer; +use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementServiceServer; +use d_engine_proto::server::election::raft_election_service_server::RaftElectionServiceServer; +use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationServiceServer; +use d_engine_proto::storage::snapshot_service_server::SnapshotServiceServer; /// RPC server works for RAFT protocol /// It mainly listens on two request: Vote RPC Request and Append Entries RPC @@ -109,7 +104,9 @@ where if config.tls.enable_tls { if config.tls.generate_self_signed_certificates { if Path::new(&config.tls.certificate_authority_root_path).exists() { - warn!("Certificate authority root already exists, remove the file if you want to generate new certificates. Skipping self signed certificates generation."); + warn!( + "Certificate authority root already exists, remove the file if you want to generate new certificates. Skipping self signed certificates generation." + ); } else { info!("Generating self signed certificates"); generate_self_signed_certificates(config.tls.clone()); diff --git a/src/network/health_checker.rs b/d-engine-runtime/src/network/health_checker.rs similarity index 98% rename from src/network/health_checker.rs rename to d-engine-runtime/src/network/health_checker.rs index 32a9ea1c..96857a75 100644 --- a/src/network/health_checker.rs +++ b/d-engine-runtime/src/network/health_checker.rs @@ -6,16 +6,16 @@ use mockall::automock; use mockall::predicate::*; use tonic::async_trait; use tonic::transport::Channel; -use tonic_health::pb::health_check_response::ServingStatus; -use tonic_health::pb::health_client::HealthClient; use tonic_health::pb::HealthCheckRequest; use tonic_health::pb::HealthCheckResponse; +use tonic_health::pb::health_check_response::ServingStatus; +use tonic_health::pb::health_client::HealthClient; use tracing::error; -use crate::net::address_str; use crate::NetworkConfig; -use crate::NetworkError; -use crate::Result; +use crate::net::address_str; +use d_engine_core::NetworkError; +use d_engine_core::Result; #[cfg_attr(test, automock)] #[async_trait] diff --git a/src/network/health_checker_test.rs b/d-engine-runtime/src/network/health_checker_test.rs similarity index 100% rename from src/network/health_checker_test.rs rename to d-engine-runtime/src/network/health_checker_test.rs diff --git a/src/network/health_monitor.rs b/d-engine-runtime/src/network/health_monitor.rs similarity index 100% rename from src/network/health_monitor.rs rename to d-engine-runtime/src/network/health_monitor.rs diff --git a/src/network/health_monitor_test.rs b/d-engine-runtime/src/network/health_monitor_test.rs similarity index 100% rename from src/network/health_monitor_test.rs rename to d-engine-runtime/src/network/health_monitor_test.rs diff --git a/src/network/mod.rs b/d-engine-runtime/src/network/mod.rs similarity index 100% rename from src/network/mod.rs rename to d-engine-runtime/src/network/mod.rs diff --git a/src/network/network_test.rs b/d-engine-runtime/src/network/network_test.rs similarity index 54% rename from src/network/network_test.rs rename to d-engine-runtime/src/network/network_test.rs index e53af65c..6f0583c4 100644 --- a/src/network/network_test.rs +++ b/d-engine-runtime/src/network/network_test.rs @@ -1,8 +1,8 @@ use tracing_test::traced_test; use super::*; -use crate::proto::cluster::cluster_conf_update_response::ErrorCode; -use crate::proto::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::cluster::ClusterConfUpdateResponse; +use d_engine_proto::server::cluster::cluster_conf_update_response::ErrorCode; async fn async_ok( number: u64 @@ -19,8 +19,8 @@ async fn async_ok( Ok(response) } -async fn async_err( -) -> std::result::Result, tonic::Status> { +async fn async_err() +-> std::result::Result, tonic::Status> { sleep(Duration::from_millis(100)).await; Err(tonic::Status::aborted("message")) } @@ -38,22 +38,26 @@ async fn test_rpc_task_with_exponential_backoff() { max_delay_ms: 3000, }; - assert!(grpc_task_with_timeout_and_exponential_backoff( - "test_rpc_task_with_exponential_backoff", - || async { async_ok(3).await }, - policy, - ) - .await - .is_ok()); + assert!( + grpc_task_with_timeout_and_exponential_backoff( + "test_rpc_task_with_exponential_backoff", + || async { async_ok(3).await }, + policy, + ) + .await + .is_ok() + ); // Case 2: when err task return error - assert!(grpc_task_with_timeout_and_exponential_backoff( - "test_rpc_task_with_exponential_backoff", - async_err, - policy - ) - .await - .is_err()); + assert!( + grpc_task_with_timeout_and_exponential_backoff( + "test_rpc_task_with_exponential_backoff", + async_err, + policy + ) + .await + .is_err() + ); // Case 3: when ok task always failed on timeout error let policy = BackoffPolicy { @@ -62,11 +66,13 @@ async fn test_rpc_task_with_exponential_backoff() { base_delay_ms: 1, max_delay_ms: 3, }; - assert!(grpc_task_with_timeout_and_exponential_backoff( - "test_rpc_task_with_exponential_backoff", - || async { async_ok(3).await }, - policy - ) - .await - .is_err()); + assert!( + grpc_task_with_timeout_and_exponential_backoff( + "test_rpc_task_with_exponential_backoff", + || async { async_ok(3).await }, + policy + ) + .await + .is_err() + ); } diff --git a/src/node/builder.rs b/d-engine-runtime/src/node/builder.rs similarity index 99% rename from src/node/builder.rs rename to d-engine-runtime/src/node/builder.rs index b3bb7f0a..e2b097a6 100644 --- a/src/node/builder.rs +++ b/d-engine-runtime/src/node/builder.rs @@ -36,25 +36,17 @@ //! config. use std::fmt::Debug; -use std::sync::atomic::AtomicBool; use std::sync::Arc; +use std::sync::atomic::AtomicBool; +use tokio::sync::Mutex; use tokio::sync::mpsc; use tokio::sync::watch; -use tokio::sync::Mutex; use tracing::debug; use tracing::error; use tracing::info; use super::RaftTypeConfig; -use crate::alias::MOF; -use crate::alias::SMHOF; -use crate::alias::SNP; -use crate::alias::TROF; -use crate::follower_state::FollowerState; -use crate::grpc; -use crate::grpc::grpc_transport::GrpcTransport; -use crate::learner_state::LearnerState; use crate::BufferedRaftLog; use crate::ClusterConfig; use crate::CommitHandler; @@ -75,11 +67,19 @@ use crate::RaftNodeConfig; use crate::RaftRole; use crate::RaftStorageHandles; use crate::ReplicationHandler; -use crate::Result; use crate::SignalParams; use crate::StateMachine; use crate::StorageEngine; use crate::SystemError; +use crate::alias::MOF; +use crate::alias::SMHOF; +use crate::alias::SNP; +use crate::alias::TROF; +use crate::follower_state::FollowerState; +use crate::grpc; +use crate::grpc::grpc_transport::GrpcTransport; +use crate::learner_state::LearnerState; +use d_engine_core::Result; pub enum NodeMode { Joiner, diff --git a/src/node/builder_test.rs b/d-engine-runtime/src/node/builder_test.rs similarity index 100% rename from src/node/builder_test.rs rename to d-engine-runtime/src/node/builder_test.rs diff --git a/src/node/mod.rs b/d-engine-runtime/src/node/mod.rs similarity index 84% rename from src/node/mod.rs rename to d-engine-runtime/src/node/mod.rs index 4dd42b23..c110f114 100644 --- a/src/node/mod.rs +++ b/d-engine-runtime/src/node/mod.rs @@ -34,20 +34,20 @@ mod builder_test; mod node_test; use std::fmt::Debug; +use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; -use std::sync::Arc; -use tokio::sync::mpsc; use tokio::sync::Mutex; +use tokio::sync::mpsc; -use crate::alias::MOF; -use crate::Membership; -use crate::Raft; -use crate::RaftEvent; -use crate::RaftNodeConfig; -use crate::Result; -use crate::TypeConfig; +use d_engine_core::Membership; +use d_engine_core::Raft; +use d_engine_core::RaftEvent; +use d_engine_core::RaftNodeConfig; +use d_engine_core::Result; +use d_engine_core::TypeConfig; +use d_engine_core::alias::MOF; /// Raft node container pub struct Node @@ -160,4 +160,22 @@ where pub fn server_is_ready(&self) -> bool { self.ready.load(Ordering::Acquire) } + + /// Create a Node from a pre-built Raft instance + /// This method is designed to support testing and external builders + pub fn from_raft(raft: Raft) -> Self { + let event_tx = raft.event_tx.clone(); + let node_config = raft.ctx.node_config(); + let membership = raft.ctx.membership(); + let node_id = raft.node_id; + + Node { + node_id, + raft_core: Arc::new(Mutex::new(raft)), + membership, + event_tx, + ready: AtomicBool::new(false), + node_config, + } + } } diff --git a/src/node/node_test.rs b/d-engine-runtime/src/node/node_test.rs similarity index 95% rename from src/node/node_test.rs rename to d-engine-runtime/src/node/node_test.rs index b79b2790..f6d5c409 100644 --- a/src/node/node_test.rs +++ b/d-engine-runtime/src/node/node_test.rs @@ -6,25 +6,25 @@ use tokio::sync::watch; use tracing::debug; use tracing_test::traced_test; -use crate::learner_state::LearnerState; -use crate::proto::cluster::JoinResponse; -use crate::proto::cluster::NodeMeta; -use crate::proto::common::NodeStatus; -use crate::test_utils::mock_membership; -use crate::test_utils::mock_node; -use crate::test_utils::MockBuilder; -use crate::test_utils::MockTypeConfig; use crate::AppendResults; use crate::Error; -use crate::MockMembership; +use crate::Follower.into(); +use crate::Learner.into(); use crate::MockRaftLog; use crate::MockReplicationCore; use crate::MockTransport; use crate::PeerUpdate; use crate::RaftNodeConfig; use crate::RaftRole; -use crate::FOLLOWER; -use crate::LEARNER; +use crate::learner_state::LearnerState; +use crate::test_utils::MockBuilder; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::mock_membership; +use crate::test_utils::mock_node; +use d_engine_core::MockMembership; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::server::cluster::JoinResponse; +use d_engine_proto::server::cluster::NodeMeta; #[tokio::test] #[traced_test] @@ -111,13 +111,13 @@ async fn run_success_without_joining() { NodeMeta { id: 100, address: "127.0.0.1:8080".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active as i32, }, NodeMeta { id: 200, address: "127.0.0.1:8081".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active as i32, }, ]; @@ -197,13 +197,13 @@ async fn run_success_with_joining() { NodeMeta { id: node_id, address: "127.0.0.1:8080".to_string(), - role: LEARNER, + role: Learner.into(), status: NodeStatus::Joining as i32, }, NodeMeta { id: 200, address: "127.0.0.1:8081".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active as i32, }, ]; @@ -285,13 +285,13 @@ async fn run_fails_on_health_check() { NodeMeta { id: 100, address: "127.0.0.1:8080".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active as i32, }, NodeMeta { id: 200, address: "127.0.0.1:8081".to_string(), - role: FOLLOWER, + role: Follower.into(), status: NodeStatus::Active as i32, }, ]; diff --git a/src/node/type_config/mod.rs b/d-engine-runtime/src/node/type_config/mod.rs similarity index 100% rename from src/node/type_config/mod.rs rename to d-engine-runtime/src/node/type_config/mod.rs diff --git a/src/node/type_config/raft_type_config.rs b/d-engine-runtime/src/node/type_config/raft_type_config.rs similarity index 97% rename from src/node/type_config/raft_type_config.rs rename to d-engine-runtime/src/node/type_config/raft_type_config.rs index fbc022f5..b7bc7f74 100644 --- a/src/node/type_config/raft_type_config.rs +++ b/d-engine-runtime/src/node/type_config/raft_type_config.rs @@ -1,6 +1,5 @@ use std::fmt::Debug; -use crate::grpc::grpc_transport::GrpcTransport; use crate::BufferedRaftLog; use crate::DefaultCommitHandler; use crate::DefaultPurgeExecutor; @@ -11,7 +10,8 @@ use crate::RaftMembership; use crate::ReplicationHandler; use crate::StateMachine; use crate::StorageEngine; -use crate::TypeConfig; +use crate::grpc::grpc_transport::GrpcTransport; +use d_engine_core::TypeConfig; #[derive(Debug)] pub struct RaftTypeConfig diff --git a/src/storage/adaptors/file/file_engine_test.rs b/d-engine-runtime/src/storage/adaptors/file/file_engine_test.rs similarity index 100% rename from src/storage/adaptors/file/file_engine_test.rs rename to d-engine-runtime/src/storage/adaptors/file/file_engine_test.rs diff --git a/src/storage/adaptors/file/file_state_machine.rs b/d-engine-runtime/src/storage/adaptors/file/file_state_machine.rs similarity index 98% rename from src/storage/adaptors/file/file_state_machine.rs rename to d-engine-runtime/src/storage/adaptors/file/file_state_machine.rs index 4b2661a8..8074cadc 100644 --- a/src/storage/adaptors/file/file_state_machine.rs +++ b/d-engine-runtime/src/storage/adaptors/file/file_state_machine.rs @@ -21,17 +21,17 @@ use tracing::info; use tracing::trace; use tracing::warn; -use crate::proto::client::write_command::Delete; -use crate::proto::client::write_command::Insert; -use crate::proto::client::write_command::Operation; -use crate::proto::client::WriteCommand; -use crate::proto::common::entry_payload::Payload; -use crate::proto::common::Entry; -use crate::proto::common::LogId; -use crate::proto::storage::SnapshotMetadata; use crate::Error; use crate::StateMachine; use crate::StorageError; +use d_engine_proto::client::WriteCommand; +use d_engine_proto::client::write_command::Delete; +use d_engine_proto::client::write_command::Insert; +use d_engine_proto::client::write_command::Operation; +use d_engine_proto::common::Entry; +use d_engine_proto::common::LogId; +use d_engine_proto::common::entry_payload::Payload; +use d_engine_proto::storage::SnapshotMetadata; type FileStateMachineDataType = RwLock>; diff --git a/src/storage/adaptors/file/file_state_machine_test.rs b/d-engine-runtime/src/storage/adaptors/file/file_state_machine_test.rs similarity index 90% rename from src/storage/adaptors/file/file_state_machine_test.rs rename to d-engine-runtime/src/storage/adaptors/file/file_state_machine_test.rs index e0b7f41d..57b34797 100644 --- a/src/storage/adaptors/file/file_state_machine_test.rs +++ b/d-engine-runtime/src/storage/adaptors/file/file_state_machine_test.rs @@ -1,14 +1,14 @@ use bytes::Bytes; use prost::Message; -use crate::proto::client::write_command::Insert; -use crate::proto::client::write_command::Operation; -use crate::proto::client::WriteCommand; -use crate::proto::common::entry_payload::Payload; -use crate::proto::common::Entry; -use crate::proto::common::EntryPayload; use crate::FileStateMachine; use crate::StateMachine; +use d_engine_proto::client::WriteCommand; +use d_engine_proto::client::write_command::Insert; +use d_engine_proto::client::write_command::Operation; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::entry_payload::Payload; #[tokio::test] async fn test_wal_replay_after_crash() { diff --git a/src/storage/adaptors/file/file_storage_engine.rs b/d-engine-runtime/src/storage/adaptors/file/file_storage_engine.rs similarity index 99% rename from src/storage/adaptors/file/file_storage_engine.rs rename to d-engine-runtime/src/storage/adaptors/file/file_storage_engine.rs index 3b35b4b1..ae05e8f1 100644 --- a/src/storage/adaptors/file/file_storage_engine.rs +++ b/d-engine-runtime/src/storage/adaptors/file/file_storage_engine.rs @@ -10,23 +10,23 @@ use std::io::Write; use std::ops::RangeInclusive; use std::path::Path; use std::path::PathBuf; -use std::sync::atomic::AtomicU64; -use std::sync::atomic::Ordering; use std::sync::Arc; use std::sync::Mutex; +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering; use prost::Message; use tonic::async_trait; use tracing::info; -use crate::proto::common::Entry; -use crate::proto::common::LogId; use crate::Error; use crate::HardState; use crate::LogStore; use crate::MetaStore; use crate::StorageEngine; use crate::StorageError; +use d_engine_proto::common::Entry; +use d_engine_proto::common::LogId; // Constants for file structure const HARD_STATE_FILE_NAME: &str = "hard_state.bin"; diff --git a/src/storage/adaptors/file/file_storage_engine_test.rs b/d-engine-runtime/src/storage/adaptors/file/file_storage_engine_test.rs similarity index 98% rename from src/storage/adaptors/file/file_storage_engine_test.rs rename to d-engine-runtime/src/storage/adaptors/file/file_storage_engine_test.rs index c9fe16b7..7c220541 100644 --- a/src/storage/adaptors/file/file_storage_engine_test.rs +++ b/d-engine-runtime/src/storage/adaptors/file/file_storage_engine_test.rs @@ -6,14 +6,14 @@ use tempfile::TempDir; use tracing_test::traced_test; use super::*; -use crate::proto::common::Entry; -use crate::proto::common::EntryPayload; -use crate::proto::common::LogId; -use crate::proto::election::VotedFor; use crate::HardState; use crate::LogStore; use crate::MetaStore; use crate::StorageEngine; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::LogId; +use d_engine_proto::server::election::VotedFor; // Helper to create test entries fn create_entries(range: RangeInclusive) -> Vec { diff --git a/src/storage/adaptors/file/mod.rs b/d-engine-runtime/src/storage/adaptors/file/mod.rs similarity index 100% rename from src/storage/adaptors/file/mod.rs rename to d-engine-runtime/src/storage/adaptors/file/mod.rs diff --git a/src/storage/adaptors/mod.rs b/d-engine-runtime/src/storage/adaptors/mod.rs similarity index 100% rename from src/storage/adaptors/mod.rs rename to d-engine-runtime/src/storage/adaptors/mod.rs diff --git a/src/storage/adaptors/rocksdb/mod.rs b/d-engine-runtime/src/storage/adaptors/rocksdb/mod.rs similarity index 100% rename from src/storage/adaptors/rocksdb/mod.rs rename to d-engine-runtime/src/storage/adaptors/rocksdb/mod.rs diff --git a/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs b/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs similarity index 97% rename from src/storage/adaptors/rocksdb/rocksdb_engine_test.rs rename to d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs index ccafd377..a21ed315 100644 --- a/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs +++ b/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs @@ -9,13 +9,13 @@ use tracing_test::traced_test; use uuid::Uuid; use super::*; -use crate::proto::client::write_command::Insert; -use crate::proto::common::Entry; -use crate::storage::storage_engine_test::StorageEngineBuilder; -use crate::storage::storage_engine_test::StorageEngineTestSuite; use crate::Error; use crate::LogStore; use crate::StorageEngine; +use crate::storage::storage_engine_test::StorageEngineBuilder; +use crate::storage::storage_engine_test::StorageEngineTestSuite; +use d_engine_proto::client::write_command::Insert; +use d_engine_proto::common::Entry; pub struct RocksDBStorageEngineBuilder { temp_dir: TempDir, diff --git a/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs b/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs similarity index 97% rename from src/storage/adaptors/rocksdb/rocksdb_state_machine.rs rename to d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs index 171a0cba..0fef39a9 100644 --- a/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs +++ b/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs @@ -1,17 +1,17 @@ use std::path::Path; +use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; -use std::sync::Arc; use bytes::Bytes; use parking_lot::RwLock; use prost::Message; use rocksdb::Cache; +use rocksdb::DB; use rocksdb::IteratorMode; use rocksdb::Options; use rocksdb::WriteBatch; -use rocksdb::DB; use tonic::async_trait; use tracing::debug; use tracing::error; @@ -19,17 +19,17 @@ use tracing::info; use tracing::instrument; use tracing::warn; -use crate::proto::client::write_command::Delete; -use crate::proto::client::write_command::Insert; -use crate::proto::client::write_command::Operation; -use crate::proto::client::WriteCommand; -use crate::proto::common::entry_payload::Payload; -use crate::proto::common::Entry; -use crate::proto::common::LogId; -use crate::proto::storage::SnapshotMetadata; use crate::Error; use crate::StateMachine; use crate::StorageError; +use d_engine_proto::client::WriteCommand; +use d_engine_proto::client::write_command::Delete; +use d_engine_proto::client::write_command::Insert; +use d_engine_proto::client::write_command::Operation; +use d_engine_proto::common::Entry; +use d_engine_proto::common::LogId; +use d_engine_proto::common::entry_payload::Payload; +use d_engine_proto::storage::SnapshotMetadata; const STATE_MACHINE_CF: &str = "state_machine"; const STATE_MACHINE_META_CF: &str = "state_machine_meta"; diff --git a/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs b/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs similarity index 99% rename from src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs rename to d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs index 498e6e86..6a3384a2 100644 --- a/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs +++ b/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs @@ -1,27 +1,27 @@ use std::ops::RangeInclusive; use std::path::Path; +use std::sync::Arc; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; -use std::sync::Arc; use prost::Message; use rocksdb::Cache; +use rocksdb::DB; use rocksdb::Direction; use rocksdb::IteratorMode; use rocksdb::Options; use rocksdb::WriteBatch; -use rocksdb::DB; use tonic::async_trait; use tracing::instrument; -use crate::proto::common::Entry; -use crate::proto::common::LogId; use crate::Error; use crate::HardState; use crate::LogStore; use crate::MetaStore; use crate::StorageEngine; use crate::StorageError; +use d_engine_proto::common::Entry; +use d_engine_proto::common::LogId; const LOG_CF: &str = "logs"; const META_CF: &str = "meta"; diff --git a/src/storage/mod.rs b/d-engine-runtime/src/storage/mod.rs similarity index 100% rename from src/storage/mod.rs rename to d-engine-runtime/src/storage/mod.rs diff --git a/src/test_utils/mock/mock_builder.rs b/d-engine-runtime/src/test_utils/mock/mock_node_builder.rs similarity index 100% rename from src/test_utils/mock/mock_builder.rs rename to d-engine-runtime/src/test_utils/mock/mock_node_builder.rs diff --git a/d-engine-runtime/src/test_utils/mock/mod.rs b/d-engine-runtime/src/test_utils/mock/mod.rs new file mode 100644 index 00000000..3eee3bd4 --- /dev/null +++ b/d-engine-runtime/src/test_utils/mock/mod.rs @@ -0,0 +1,39 @@ +mod mock_node_builder; +pub use mock_node_builder::*; + +use crate::Node; +use d_engine_core::{ + Raft, + test_utils::{MockTypeConfig, node_config}, +}; +use d_engine_proto::server::cluster::NodeMeta; +use tokio::sync::watch; + +pub(crate) fn mock_node( + db_path: &str, + shutdown_signal: watch::Receiver<()>, + peers_meta_option: Option>, +) -> Node { + let mut node_config = node_config(db_path); + if let Some(peers_meta) = peers_meta_option { + node_config.cluster.initial_cluster = peers_meta; + } + // Initializing Shutdown Signal + // let (graceful_tx, graceful_rx) = watch::channel(()); + MockBuilder::new(shutdown_signal).with_node_config(node_config).build_node() +} + +pub(crate) fn mock_raft( + db_path: &str, + shutdown_signal: watch::Receiver<()>, + peers_meta_option: Option>, +) -> Raft { + let mut node_config = node_config(db_path); + // Set batch_threshold=0, means the replication will be triggered immediatelly. + node_config.raft.replication.rpc_append_entries_in_batch_threshold = 0; + if let Some(peers_meta) = peers_meta_option { + node_config.cluster.initial_cluster = peers_meta; + } + + MockBuilder::new(shutdown_signal).with_node_config(node_config).build_raft() +} diff --git a/d-engine-runtime/src/test_utils/mod.rs b/d-engine-runtime/src/test_utils/mod.rs new file mode 100644 index 00000000..07604006 --- /dev/null +++ b/d-engine-runtime/src/test_utils/mod.rs @@ -0,0 +1,3 @@ +mod mock; + +pub use mock::*; diff --git a/src/utils/async_task.rs b/d-engine-runtime/src/utils/async_task.rs similarity index 97% rename from src/utils/async_task.rs rename to d-engine-runtime/src/utils/async_task.rs index 9188efa5..24c7c98d 100644 --- a/src/utils/async_task.rs +++ b/d-engine-runtime/src/utils/async_task.rs @@ -7,8 +7,8 @@ use tracing::error; use tracing::warn; use crate::BackoffPolicy; -use crate::NetworkError; -use crate::Result; +use d_engine_core::NetworkError; +use d_engine_core::Result; /// General one pub(crate) async fn task_with_timeout_and_exponential_backoff( diff --git a/src/utils/async_task_test.rs b/d-engine-runtime/src/utils/async_task_test.rs similarity index 100% rename from src/utils/async_task_test.rs rename to d-engine-runtime/src/utils/async_task_test.rs diff --git a/src/utils/cluster.rs b/d-engine-runtime/src/utils/cluster.rs similarity index 85% rename from src/utils/cluster.rs rename to d-engine-runtime/src/utils/cluster.rs index ff0b5f2e..993abce4 100644 --- a/src/utils/cluster.rs +++ b/d-engine-runtime/src/utils/cluster.rs @@ -6,11 +6,11 @@ use dashmap::DashSet; use metrics::gauge; use tracing::error; -use crate::proto::common::Entry; -use crate::CANDIDATE; -use crate::FOLLOWER; -use crate::LEADER; -use crate::LEARNER; +use crate::Candidate.into(); +use crate::Follower.into(); +use crate::Leader.into(); +use crate::Learner.into(); +use d_engine_proto::common::Entry; pub(crate) fn collect_ids(entries: &[Entry]) -> Vec { entries.iter().map(|e| e.index).collect() @@ -64,20 +64,20 @@ pub fn error( #[inline] pub fn is_follower(role_i32: i32) -> bool { - role_i32 == FOLLOWER + role_i32 == Follower.into() } #[inline] pub fn is_candidate(role_i32: i32) -> bool { - role_i32 == CANDIDATE + role_i32 == Candidate.into() } #[inline] pub fn is_leader(role_i32: i32) -> bool { - role_i32 == LEADER + role_i32 == Leader.into() } #[inline] pub fn is_learner(role_i32: i32) -> bool { - role_i32 == LEARNER + role_i32 == Learner.into() } diff --git a/src/utils/mod.rs b/d-engine-runtime/src/utils/mod.rs similarity index 74% rename from src/utils/mod.rs rename to d-engine-runtime/src/utils/mod.rs index 0fb4eac2..fe96667a 100644 --- a/src/utils/mod.rs +++ b/d-engine-runtime/src/utils/mod.rs @@ -13,15 +13,9 @@ pub mod file_io; #[allow(dead_code)] pub mod net; -#[allow(dead_code)] -pub mod time; - #[allow(dead_code)] pub mod scoped_timer; -#[allow(dead_code)] -pub mod stream; - #[cfg(test)] mod utils_test; @@ -36,9 +30,3 @@ mod convert_test; #[cfg(test)] mod net_test; - -#[cfg(test)] -mod stream_test; - -#[cfg(test)] -mod time_test; diff --git a/src/utils/net.rs b/d-engine-runtime/src/utils/net.rs similarity index 100% rename from src/utils/net.rs rename to d-engine-runtime/src/utils/net.rs diff --git a/src/utils/net_test.rs b/d-engine-runtime/src/utils/net_test.rs similarity index 100% rename from src/utils/net_test.rs rename to d-engine-runtime/src/utils/net_test.rs diff --git a/src/utils/scoped_timer.rs b/d-engine-runtime/src/utils/scoped_timer.rs similarity index 100% rename from src/utils/scoped_timer.rs rename to d-engine-runtime/src/utils/scoped_timer.rs diff --git a/src/utils/utils_test.rs b/d-engine-runtime/src/utils/utils_test.rs similarity index 97% rename from src/utils/utils_test.rs rename to d-engine-runtime/src/utils/utils_test.rs index 619b195e..16011dd6 100644 --- a/src/utils/utils_test.rs +++ b/d-engine-runtime/src/utils/utils_test.rs @@ -6,14 +6,14 @@ use tokio::time::sleep; use tracing_test::traced_test; use super::cluster::find_nearest_lower_number; +use crate::BackoffPolicy; use crate::async_task::task_with_timeout_and_exponential_backoff; use crate::convert::abs_ceil; use crate::convert::safe_kv; use crate::convert::safe_vk; use crate::utils::cluster::is_majority; -use crate::BackoffPolicy; -use crate::NetworkError; -use crate::Result; +use d_engine_core::NetworkError; +use d_engine_core::Result; #[test] fn test_kv_1() { diff --git a/src/constants.rs b/src/constants.rs deleted file mode 100644 index 29bd0cea..00000000 --- a/src/constants.rs +++ /dev/null @@ -1,2 +0,0 @@ -/// Snapshot dir -pub(crate) const SNAPSHOT_DIR_PREFIX: &str = "snapshot-"; diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index c3c4f4c0..00000000 --- a/src/lib.rs +++ /dev/null @@ -1,42 +0,0 @@ -#![doc = include_str!("docs/overview.md")] - -// #![warn(missing_docs)] - -pub mod client; -pub mod config; -pub mod docs; -pub mod node; -pub mod proto; -pub mod storage; - -#[doc(hidden)] -pub use client::*; -#[doc(hidden)] -pub use config::*; -#[doc(hidden)] -pub use node::*; - -mod constants; -mod core; -mod errors; -mod membership; -mod network; - -#[doc(hidden)] -pub use core::*; - -pub use errors::*; -pub(crate) use membership::*; -pub(crate) use network::*; -pub use storage::*; - -#[doc(hidden)] -pub mod utils; -#[doc(hidden)] -pub use utils::*; - -//----------------------------------------------------------- -// Test utils -#[cfg(test)] -#[doc(hidden)] -pub mod test_utils; From a6cc0cebc1bcd72229863cc9390feccbcc443644 Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Thu, 23 Oct 2025 11:28:46 +0800 Subject: [PATCH 06/26] feat #167: 1/ migrate buffered_raft_log from core to runtime 2/ start creating integration tests folder inside runtime --- d-engine-core/src/storage/mod.rs | 1 - d-engine-core/src/storage/raft_log.rs | 440 +++++++++++++++--- .../src/storage/buffered/buffered_raft_log.rs | 20 + .../buffered/buffered_raft_log_test.rs | 24 +- .../src/storage/buffered/mod.rs | 0 d-engine-runtime/src/storage/mod.rs | 15 - 6 files changed, 413 insertions(+), 87 deletions(-) rename {d-engine-core => d-engine-runtime}/src/storage/buffered/buffered_raft_log.rs (98%) rename {d-engine-core => d-engine-runtime}/src/storage/buffered/buffered_raft_log_test.rs (99%) rename {d-engine-core => d-engine-runtime}/src/storage/buffered/mod.rs (100%) diff --git a/d-engine-core/src/storage/mod.rs b/d-engine-core/src/storage/mod.rs index b68a3cf9..bbec6efa 100644 --- a/d-engine-core/src/storage/mod.rs +++ b/d-engine-core/src/storage/mod.rs @@ -1,4 +1,3 @@ -mod buffered; mod raft_log; mod snapshot_path_manager; mod state_machine; diff --git a/d-engine-core/src/storage/raft_log.rs b/d-engine-core/src/storage/raft_log.rs index 52842119..6896f175 100644 --- a/d-engine-core/src/storage/raft_log.rs +++ b/d-engine-core/src/storage/raft_log.rs @@ -1,125 +1,447 @@ -//! Core model in Raft: RaftLog Definition - -use std::ops::RangeInclusive; - -#[cfg(test)] -use mockall::automock; -use tonic::async_trait; +//! RaftLog trait with explicit safety contracts and documentation +//! +//! This trait defines the interface for Raft log storage with comprehensive +//! safety guarantees. Implementers MUST adhere to all documented invariants +//! to maintain Raft consensus protocol correctness. +//! +//! Key Safety Properties (from Raft paper): +//! 1. Log Matching: If two logs contain an entry with same index and term, +//! then logs are identical in all preceding entries +//! 2. Leader Append-Only: Leaders never overwrite or delete entries +//! 3. State Machine Safety: If a server has applied a log entry at a given +//! index, no other server will ever apply a different log entry for that index use crate::Result; use d_engine_proto::common::Entry; -use d_engine_proto::common::EntryPayload; use d_engine_proto::common::LogId; -use d_engine_proto::common::entry_payload::Payload; - -// /// Configurable persistence strategy for Raft logs -// #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] -// pub enum PersistenceStrategy { -// /// Persist logs to disk before replicating to followers -// /// (Stronger durability, lower throughput) -// DiskFirst, - -// /// Replicate logs immediately and persist asynchronously -// /// (Higher throughput, optimized for low-latency networks) -// MemFirst, - -// /// Persist logs in batches at fixed intervals -// /// (Balanced approach for mixed workloads) -// Batched(usize, u64), // (batch_size, interval_ms) -// } - -// impl Default for PersistenceStrategy { -// fn default() -> Self { -// Self::Batched(1000, 10) -// } -// } +#[cfg(test)] +use mockall::automock; +use std::ops::RangeInclusive; +use tonic::async_trait; #[cfg_attr(test, automock)] #[async_trait] pub trait RaftLog: Send + Sync + 'static { + // ========================================================================= + // READ OPERATIONS (Lock-free, no side effects) + // ========================================================================= + + /// Retrieves a log entry by index. + /// + /// # Returns + /// - `Ok(Some(entry))` if entry exists + /// - `Ok(None)` if index is out of range + /// - `Err(_)` only for unrecoverable storage errors + /// + /// # Safety Invariants + /// - MUST be thread-safe and lock-free for maximum performance + /// - MUST NOT modify any state + /// - MUST return consistent data within a single term fn entry( &self, index: u64, ) -> Result>; + /// Returns the smallest log index (inclusive). + /// + /// # Returns + /// - `0` if log is empty + /// - First index otherwise (typically 1 after initialization) + /// + /// # Safety Invariants + /// - MUST be monotonically non-decreasing (can only increase via purge) + /// - MUST be <= last_entry_id() fn first_entry_id(&self) -> u64; + /// Returns the largest log index (inclusive). + /// + /// # Returns + /// - `0` if log is empty + /// - Last index otherwise + /// + /// # Safety Invariants + /// - MUST be monotonically non-decreasing during normal operation + /// - Can decrease only during conflict resolution (filter_out_conflicts_and_append) + /// - MUST be >= first_entry_id() fn last_entry_id(&self) -> u64; + /// Returns the LogId (term + index) of the last entry. + /// + /// # Returns + /// - `None` if log is empty + /// - `Some(LogId)` otherwise + /// + /// # Safety Invariants + /// - MUST match last_entry().map(|e| LogId { term: e.term, index: e.index }) + /// - Critical for leader election and log matching fn last_log_id(&self) -> Option; + /// Returns the last log entry. + /// + /// # Safety Invariants + /// - MUST be equivalent to entry(last_entry_id()) + /// - Performance optimization for common access pattern fn last_entry(&self) -> Option; + /// Checks if log is empty. + /// + /// # Safety Invariants + /// - MUST be equivalent to (first_entry_id() == 0) fn is_empty(&self) -> bool; + /// Returns the term of a specific entry. + /// + /// # Safety Invariants + /// - MUST be equivalent to entry(entry_id).map(|e| e.term) + /// - Critical for AppendEntries RPC consistency checks fn entry_term( &self, entry_id: u64, ) -> Option; - - fn pre_allocate_raft_logs_next_index(&self) -> u64; - - /// Pre-allocates a contiguous range of log indices - /// Returns inclusive range [start, end] where (end - start + 1) == count - fn pre_allocate_id_range( - &self, - count: u64, - ) -> RangeInclusive; - - fn last_index_for_term( + /// Finds the first index belonging to a specific term. + /// + /// # Returns + /// - `None` if no entry with this term exists + /// - `Some(index)` of the first entry with matching term + /// + /// # Safety Invariants + /// - MUST scan forward from first_entry_id() + /// - Result MUST be <= last_index_for_term(term) + /// - Critical for calculating commit index + fn first_index_for_term( &self, term: u64, ) -> Option; - fn first_index_for_term( + /// Finds the last index belonging to a specific term. + /// + /// # Returns + /// - `None` if no entry with this term exists + /// - `Some(index)` of the last entry with matching term + /// + /// # Safety Invariants + /// - MUST scan backward from last_entry_id() + /// - Result MUST be >= first_index_for_term(term) + /// - Critical for leader election and log compaction + fn last_index_for_term( &self, term: u64, ) -> Option; + /// Retrieves a contiguous range of log entries. + /// + /// # Arguments + /// - `range`: Inclusive range [start, end] + /// + /// # Returns + /// - `Ok(Vec)` with all entries in range + /// - Empty vec if range is out of bounds + /// + /// # Safety Invariants + /// - Returned entries MUST be ordered by index (ascending) + /// - MUST include all entries in range without gaps + /// - Critical for AppendEntries RPC replication fn get_entries_range( &self, range: RangeInclusive, ) -> Result>; - async fn filter_out_conflicts_and_append( + // ========================================================================= + // INDEX ALLOCATION (Thread-safe, atomic) + // ========================================================================= + + /// Pre-allocates the next log index atomically. + /// + /// # Returns + /// - Next available index + /// + /// # Safety Invariants + /// - MUST be thread-safe (atomic increment) + /// - MUST return unique, monotonically increasing values + /// - Each call MUST return a different value across all threads + /// - Used by leader to assign indexes to client proposals + fn pre_allocate_raft_logs_next_index(&self) -> u64; + + /// Pre-allocates a contiguous range of log indices atomically. + /// + /// # Arguments + /// - `count`: Number of indices to allocate + /// + /// # Returns + /// - Inclusive range [start, end] where (end - start + 1) == count + /// - Empty range (u64::MAX..=u64::MAX) if count == 0 + /// + /// # Safety Invariants + /// - MUST be atomic across all threads + /// - MUST NOT return overlapping ranges + /// - MUST prevent overflow (panic if allocation would exceed u64::MAX) + /// - Used for batch proposal optimization + fn pre_allocate_id_range( &self, - prev_log_index: u64, - prev_log_term: u64, - new_entries: Vec, - ) -> Result>; + count: u64, + ) -> RangeInclusive; + // ========================================================================= + // WRITE OPERATIONS (CRITICAL: Durability and Atomicity Required) + // ========================================================================= + + /// Appends new entries to the log. + /// + /// # Arguments + /// - `entries`: New log entries to append (must be ordered by index) + /// + /// # Durability Contract + /// **CRITICAL**: Implementers MUST choose one of these strategies: + /// + /// 1. **Disk-First (Safest, Recommended for Leaders)**: + /// - Persist entries to durable storage BEFORE updating in-memory state + /// - Call fsync/flush before returning Ok(()) + /// - Ensures entries survive crashes immediately + /// - Example: BufferedRaftLog with PersistenceStrategy::DiskFirst + /// + /// 2. **Memory-First (Performance-optimized, Acceptable for Followers)**: + /// - Update in-memory state first + /// - Enqueue entries for asynchronous durability + /// - MUST guarantee eventual durability via background flush + /// - MUST call flush() before acknowledging commits + /// - Example: BufferedRaftLog with PersistenceStrategy::MemFirst + /// - WARNING: Leader MUST wait_durable() before responding to AppendEntries RPCs + /// + /// # Safety Invariants + /// - Entries MUST have strictly increasing indices (no gaps) + /// - Entries MUST NOT conflict with existing entries (use filter_out_conflicts_and_append for that) + /// - After successful return, entries MUST be retrievable via entry() + /// - MUST update last_entry_id() atomically + /// - MUST update term indexes (first/last_index_for_term) atomically + /// + /// # Raft Protocol Integration + /// - Leaders using MemFirst MUST call wait_durable(index) before: + /// * Responding success to AppendEntries RPC + /// * Advancing commit index + /// - Followers can use MemFirst safely because leader durability guarantees safety + /// + /// # Failure Semantics + /// - On error, implementer MAY roll back partial writes + /// - Caller MUST retry or initiate crash recovery async fn append_entries( &self, entries: Vec, ) -> Result<()>; + /// Waits for a specific log index to become durable. + /// + /// # Arguments + /// - `index`: Log index to wait for + /// + /// # Returns + /// - `Ok(())` when index is guaranteed durable + /// - `Err(_)` if waiting failed (channel closed, timeout, etc.) + /// + /// # Usage Pattern + /// ```rust,ignore + /// // Leader with MemFirst strategy + /// raft_log.append_entries(new_entries).await?; + /// raft_log.wait_durable(max_index).await?; // MUST wait before RPC response + /// respond_to_client(Ok(())); + /// ``` + /// + /// # Safety Invariants + /// - MUST NOT return until flush() for this index completes successfully + /// - If implementation doesn't support async durability, return Ok(()) immediately + /// - Critical for MemFirst strategy correctness + async fn wait_durable( + &self, + index: u64, + ) -> Result<()> { + // Default implementation for DiskFirst: no-op (already durable) + let _ = index; + Ok(()) + } + + /// Alias for append_entries (for backward compatibility). + /// + /// # Safety Invariants + /// - MUST have identical semantics to append_entries() async fn insert_batch( &self, logs: Vec, - ) -> Result<()>; + ) -> Result<()> { + self.append_entries(logs).await + } - /// Clear out all the entries in local logs before last_applied - /// this function will be invoked only when entries been applied to state - /// machine successfully + /// Resolves log conflicts and appends new entries atomically. + /// + /// This is the core of Raft's log replication safety. Implements the + /// AppendEntries RPC log consistency check and conflict resolution. + /// + /// # Arguments + /// - `prev_log_index`: Index of entry immediately preceding new entries + /// - `prev_log_term`: Term of entry at prev_log_index + /// - `new_entries`: New entries to append + /// + /// # Algorithm (from Raft paper §5.3) + /// 1. **Virtual Log Check**: If prev_log_index == 0 && prev_log_term == 0: + /// - Clear entire log (snapshot installation) + /// - Append all new entries + /// 2. **Consistency Check**: Verify entry at prev_log_index has term == prev_log_term + /// - If mismatch, return current last_log_id (reject) + /// 3. **Conflict Resolution**: If existing entries conflict with new ones: + /// - Delete all entries from prev_log_index+1 onwards + /// - Append new entries + /// 4. **Optimization**: If prev_log_index >= last_entry_id: + /// - No conflicts, directly append + /// + /// # Returns + /// - `Ok(Some(new_last_log_id))` on success (entries appended) + /// - `Ok(None)` or `Ok(Some(current_last_log_id))` on consistency failure (rejected) + /// - `Err(_)` on storage failure + /// + /// # Safety Invariants (CRITICAL) + /// - Truncation + Append MUST be atomic (no partial state visible) + /// - If crash occurs mid-operation, log MUST be in consistent state after recovery + /// - MUST NOT violate Log Matching Property (Raft invariant) + /// - MUST update term indexes atomically with log changes + /// - After success, all entries from first_entry_id to new last index MUST be contiguous + /// + /// # Recommended Implementation Pattern + /// ```rust,ignore + /// async fn filter_out_conflicts_and_append(...) -> Result> { + /// // 1. Validate consistency (read-only) + /// // 2. Begin transaction/write batch + /// // 3. Truncate conflicting entries + /// // 4. Append new entries + /// // 5. Commit transaction/flush write batch + /// // 6. Update in-memory indexes atomically + /// } + /// ``` + async fn filter_out_conflicts_and_append( + &self, + prev_log_index: u64, + prev_log_term: u64, + new_entries: Vec, + ) -> Result>; + + /// Calculates the majority-matched index for commit advancement. + /// + /// # Arguments + /// - `current_term`: Leader's current term + /// - `commit_index`: Current committed index + /// - `matched_ids`: Follower match indexes (leader's last index added automatically) + /// + /// # Algorithm (Raft paper §5.3, §5.4) + /// 1. Sort all matched indexes (including leader's) in descending order + /// 2. Find median index (majority quorum) + /// 3. Verify conditions: + /// - majority_index >= commit_index + /// - entry at majority_index has term == current_term + /// + /// # Returns + /// - `Some(new_commit_index)` if conditions met + /// - `None` if no advancement possible + /// + /// # Safety Invariants + /// - MUST verify term matches current_term (prevents committing entries from old terms) + /// - MUST NOT decrease commit index + /// - Critical for State Machine Safety property + fn calculate_majority_matched_index( + &self, + current_term: u64, + commit_index: u64, + matched_ids: Vec, + ) -> Option; + + /// Purges committed log entries up to cutoff_index (log compaction). + /// + /// # Arguments + /// - `cutoff_index`: LogId of last entry to purge (inclusive) + /// + /// # Safety Invariants (CRITICAL) + /// - MUST only be called after entries applied to state machine + /// - MUST NOT purge entries beyond last_applied_index + /// - After purge, first_entry_id() MUST be cutoff_index.index + 1 + /// - MUST be atomic (no partial purge visible) + /// - MUST NOT cause gaps in remaining log + /// - MUST update term indexes correctly + /// + /// # Durability Contract + /// - Changes MUST be durable before returning Ok(()) + /// - On crash, either old or new state MUST be consistent + /// + /// # Recommended Implementation + /// - Coordinate with snapshot mechanism + /// - Ensure snapshot covers purged range async fn purge_logs_up_to( &self, cutoff_index: LogId, ) -> Result<()>; + /// Forces all pending writes to durable storage. + /// + /// # Durability Contract + /// - After successful return, ALL previously written entries MUST survive crashes + /// - MUST call fsync/fdatasync or equivalent + /// - MUST block until durability guaranteed + /// + /// # When to Call + /// - Before acknowledging commits to clients + /// - Before voting in elections + /// - Before responding to AppendEntries RPCs + /// - Before taking snapshots + /// + /// # Performance Note + /// - This is expensive; batch writes when possible + /// - Consider group commit optimization async fn flush(&self) -> Result<()>; - /// @Write + /// Resets the entire log storage (destructive operation). + /// + /// # Safety Invariants + /// - MUST clear all log entries + /// - MUST clear all metadata + /// - MUST reset first_entry_id() and last_entry_id() to 0 + /// - MUST clear term indexes + /// - MUST be atomic (no partial state visible) + /// - MUST NOT be called during normal operation (only during initialization/recovery) + /// + /// # Durability Contract + /// - Changes MUST be durable before returning Ok(()) async fn reset(&self) -> Result<()>; - fn calculate_majority_matched_index( - &self, - current_term: u64, - commit_index: u64, - matched_ids: Vec, - ) -> Option; + // ========================================================================= + // PERSISTENT STATE (Raft Paper §5.2) + // ========================================================================= + /// Loads persistent state (currentTerm, votedFor). + /// + /// # Returns + /// - `Ok(Some(HardState))` if state exists + /// - `Ok(None)` if no state persisted yet (first boot) + /// - `Err(_)` on storage failure + /// + /// # Safety Invariants + /// - MUST return most recently saved state + /// - MUST survive crashes (durably persisted) fn load_hard_state(&self) -> Result>; + /// Saves persistent state (currentTerm, votedFor). + /// + /// # Arguments + /// - `hard_state`: Current term and vote information + /// + /// # Durability Contract (CRITICAL) + /// - MUST be durable BEFORE returning Ok(()) + /// - MUST call fsync/flush before returning + /// - On crash after return, state MUST be retrievable via load_hard_state() + /// + /// # Safety Invariants + /// - Violating durability breaks election safety (vote splitting) + /// - MUST be atomic with respect to load_hard_state() + /// - Term MUST NOT decrease (except during reset) + /// + /// # When to Call + /// - Before voting for a candidate + /// - When becoming candidate (incrementing term) + /// - When discovering higher term fn save_hard_state( &self, hard_state: &crate::HardState, diff --git a/d-engine-core/src/storage/buffered/buffered_raft_log.rs b/d-engine-runtime/src/storage/buffered/buffered_raft_log.rs similarity index 98% rename from d-engine-core/src/storage/buffered/buffered_raft_log.rs rename to d-engine-runtime/src/storage/buffered/buffered_raft_log.rs index 5de0ac1b..b044fb3a 100644 --- a/d-engine-core/src/storage/buffered/buffered_raft_log.rs +++ b/d-engine-runtime/src/storage/buffered/buffered_raft_log.rs @@ -348,6 +348,26 @@ where Ok(()) } + async fn wait_durable( + &self, + index: u64, + ) -> Result<()> { + let durable_index = self.durable_index.load(Ordering::Acquire); + if index <= durable_index { + return Ok(()); + } + + let (tx, rx) = oneshot::channel(); + self.command_sender.send(LogCommand::WaitDurable(index, tx)).map_err(|e| { + NetworkError::SingalSendFailed(format!("wait_durable send failed: {e:?}")) + })?; + + rx.await + .map_err(|_| NetworkError::SingalSendFailed("wait_durable channel closed".into()))?; + + Ok(()) + } + async fn insert_batch( &self, logs: Vec, diff --git a/d-engine-core/src/storage/buffered/buffered_raft_log_test.rs b/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs similarity index 99% rename from d-engine-core/src/storage/buffered/buffered_raft_log_test.rs rename to d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs index 1b8f4530..70393c36 100644 --- a/d-engine-core/src/storage/buffered/buffered_raft_log_test.rs +++ b/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs @@ -15,7 +15,7 @@ use tracing::debug; use tracing_test::traced_test; use super::*; -use crate::FileStorageEngine; +// use crate::FileStorageEngine; use crate::FlushPolicy; use crate::LogStore; use crate::MockLogStore; @@ -24,7 +24,7 @@ use crate::MockStateMachine; use crate::PersistenceConfig; use crate::PersistenceStrategy; use crate::RaftLog; -use crate::RaftTypeConfig; +// use crate::RaftTypeConfig; use crate::StorageEngine; use crate::alias::ROF; use crate::test_utils::MockStorageEngine; @@ -37,13 +37,13 @@ use d_engine_proto::common::LogId; // Test utilities struct TestContext { - raft_log: Arc>>, - storage: Arc, + raft_log: Arc>, + storage: Arc, _temp_dir: Option, strategy: PersistenceStrategy, flush_policy: FlushPolicy, - // Add instance ID to ensure proper crash recovery simulation - path: String, + // Instance ID for persistence simulation across "crashes" + instance_id: String, } impl TestContext { @@ -53,11 +53,11 @@ impl TestContext { instance_id: &str, ) -> Self { let temp_dir = tempdir().unwrap(); - let path = temp_dir.path().to_path_buf().join(instance_id); - // let instance_id = instance_id.to_string(); - let storage = Arc::new(FileStorageEngine::new(path.clone()).unwrap()); + let instance_id_owned = instance_id.to_string(); + + let storage = Arc::new(MockStorageEngine::with_id(instance_id_owned.clone())); - let (raft_log, receiver) = BufferedRaftLog::new( + let (raft_log, receiver) = BufferedRaftLog::::new( 1, PersistenceConfig { strategy: strategy.clone(), @@ -73,7 +73,7 @@ impl TestContext { std::thread::sleep(Duration::from_millis(10)); Self { - path: path.to_str().unwrap().to_string(), + instance_id: instance_id_owned, raft_log, storage, strategy, @@ -89,7 +89,7 @@ impl TestContext { // Use the same instance ID to simulate recovery from the same storage let storage = Arc::new(FileStorageEngine::new(PathBuf::from(self.path.clone())).unwrap()); - let (raft_log, receiver) = BufferedRaftLog::new( + let (raft_log, receiver) = BufferedRaftLog::::new( 1, PersistenceConfig { strategy: self.strategy.clone(), diff --git a/d-engine-core/src/storage/buffered/mod.rs b/d-engine-runtime/src/storage/buffered/mod.rs similarity index 100% rename from d-engine-core/src/storage/buffered/mod.rs rename to d-engine-runtime/src/storage/buffered/mod.rs diff --git a/d-engine-runtime/src/storage/mod.rs b/d-engine-runtime/src/storage/mod.rs index 86cd3480..0639f62f 100644 --- a/d-engine-runtime/src/storage/mod.rs +++ b/d-engine-runtime/src/storage/mod.rs @@ -12,23 +12,8 @@ //! //! This module is designed so developers can easily implement custom //! storage backends without changing the Raft protocol logic. - mod adaptors; mod buffered; -mod raft_log; -mod snapshot_path_manager; -mod state_machine; -mod storage_engine; pub use adaptors::*; pub(crate) use buffered::*; -pub(crate) use snapshot_path_manager::*; -pub use state_machine::*; -pub use storage_engine::*; - -#[doc(hidden)] -pub use raft_log::*; -#[doc(hidden)] -pub mod state_machine_test; -#[doc(hidden)] -pub mod storage_engine_test; From 0a160e9d39835d1329d5fae57a6f03cf933448fd Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Thu, 23 Oct 2025 11:28:53 +0800 Subject: [PATCH 07/26] feat #167: 1/ migrate buffered_raft_log from core to runtime 2/ start creating integration tests folder inside runtime --- .../components/buffered_raft_log_test.rs | 4592 +++++++++++++++++ .../tests/components/election_handler_test.rs | 682 +++ d-engine-runtime/tests/components/mod.rs | 2 + d-engine-runtime/tests/integration_test.rs | 1 + 4 files changed, 5277 insertions(+) create mode 100644 d-engine-runtime/tests/components/buffered_raft_log_test.rs create mode 100644 d-engine-runtime/tests/components/election_handler_test.rs create mode 100644 d-engine-runtime/tests/components/mod.rs create mode 100644 d-engine-runtime/tests/integration_test.rs diff --git a/d-engine-runtime/tests/components/buffered_raft_log_test.rs b/d-engine-runtime/tests/components/buffered_raft_log_test.rs new file mode 100644 index 00000000..1b8f4530 --- /dev/null +++ b/d-engine-runtime/tests/components/buffered_raft_log_test.rs @@ -0,0 +1,4592 @@ +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; +use std::time::Duration; +use std::vec; + +use bytes::Bytes; +use futures::future::join_all; +use tempfile::tempdir; +use tokio::time::Instant; +use tokio::time::sleep; +use tracing::debug; +use tracing_test::traced_test; + +use super::*; +use crate::FileStorageEngine; +use crate::FlushPolicy; +use crate::LogStore; +use crate::MockLogStore; +use crate::MockMetaStore; +use crate::MockStateMachine; +use crate::PersistenceConfig; +use crate::PersistenceStrategy; +use crate::RaftLog; +use crate::RaftTypeConfig; +use crate::StorageEngine; +use crate::alias::ROF; +use crate::test_utils::MockStorageEngine; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::generate_insert_commands; +use crate::test_utils::{self}; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::LogId; + +// Test utilities +struct TestContext { + raft_log: Arc>>, + storage: Arc, + _temp_dir: Option, + strategy: PersistenceStrategy, + flush_policy: FlushPolicy, + // Add instance ID to ensure proper crash recovery simulation + path: String, +} + +impl TestContext { + fn new( + strategy: PersistenceStrategy, + flush_policy: FlushPolicy, + instance_id: &str, + ) -> Self { + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path().to_path_buf().join(instance_id); + // let instance_id = instance_id.to_string(); + let storage = Arc::new(FileStorageEngine::new(path.clone()).unwrap()); + + let (raft_log, receiver) = BufferedRaftLog::new( + 1, + PersistenceConfig { + strategy: strategy.clone(), + flush_policy: flush_policy.clone(), + max_buffered_entries: 1000, + ..Default::default() + }, + storage.clone(), + ); + let raft_log = { raft_log.start(receiver) }; + + // Small delay to ensure processor is ready + std::thread::sleep(Duration::from_millis(10)); + + Self { + path: path.to_str().unwrap().to_string(), + raft_log, + storage, + strategy, + flush_policy, + _temp_dir: Some(temp_dir), + } + } + + // Method to create a new context that simulates recovery from the same storage + fn recover_from_crash(&self) -> Self { + let temp_dir = tempdir().unwrap(); + + // Use the same instance ID to simulate recovery from the same storage + let storage = Arc::new(FileStorageEngine::new(PathBuf::from(self.path.clone())).unwrap()); + + let (raft_log, receiver) = BufferedRaftLog::new( + 1, + PersistenceConfig { + strategy: self.strategy.clone(), + flush_policy: self.flush_policy.clone(), + max_buffered_entries: 1000, + ..Default::default() + }, + storage.clone(), + ); + let raft_log = raft_log.start(receiver); + + // Small delay to ensure processor is ready + std::thread::sleep(Duration::from_millis(10)); + + Self { + raft_log, + storage, + strategy: self.strategy.clone(), + flush_policy: self.flush_policy.clone(), + _temp_dir: Some(temp_dir), + path: self.path.clone(), + } + } + + async fn append_entries( + &self, + start: u64, + count: u64, + term: u64, + ) { + let entries: Vec<_> = (start..start + count) + .map(|index| Entry { + index, + term, + payload: Some(EntryPayload::command(Bytes::from(b"data".to_vec()))), + }) + .collect(); + + self.raft_log.append_entries(entries).await.unwrap(); + } +} + +async fn insert( + raft_log: &Arc>>, + key: u64, +) { + let log = Entry { + index: key, + term: 7, + payload: None, + }; + raft_log.insert_batch(vec![log]).await.expect("should succeed"); +} + +#[tokio::test] +#[traced_test] +async fn test_get_range1() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_get_range1", + ); + let i1: u64 = 11; + let i2: u64 = 12; + let i3: u64 = 13; + let i4: u64 = 14; + test_utils::simulate_insert_command(&context.raft_log, vec![i1, i2, i3, i4], 4).await; + let list = context.raft_log.get_entries_range(1..=2).unwrap(); + + // let mut list = Vec::::new(); + // for i in r { + // let mut l = Entry::default(); + // l.unpack(&mut Cursor::new(i)).expect("unpack failed."); + // list.push(l); + // } + assert_eq!(2, list.len()); + assert_eq!(list[0].index, 1); + assert_eq!(list[1].index, 2); +} + +#[tokio::test] +#[traced_test] +async fn test_get_range2() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_get_range2", + ); + + for i in 1..300 { + insert(&context.raft_log, i).await; + } + + let list = context.raft_log.get_entries_range(255..=255).unwrap(); + + // let mut list = Vec::::new(); + // for i in r { + // let mut l = Entry::default(); + // l.unpack(&mut Cursor::new(i)).expect("unpack failed."); + // list.push(l); + // } + assert_eq!(1, list.len()); + assert_eq!(list[0].index, 255); +} + +/// # Case 1: test follower f delete conflicts (Figure 7 in raft paper) +/// +/// ## Setup: +/// 1. leader:log1(1), log2(1), log3(1), log4(4), log5(4), log6(5), log7(5), log8(6), log9(6), +/// log10(6) +/// +/// follower_f: log1(1), log2(1), log3(1), log4(2), log5(2), log6(2), log7(3), log8(3), +/// log9(3), log10(3), log11(3) +/// +/// ## Criterias: +/// 1. next_id been updated to: +/// +/// Follower lastIndex nextIndex +/// follower_a 9 10 +/// follower_b 4 5 +/// follower_c 10 11 +/// follower_d 12 11 +/// follower_e 7 6 +/// follower_f 11 4 +#[tokio::test] +#[traced_test] +async fn test_filter_out_conflicts_and_append_case1() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_filter_out_conflicts_and_append_case1", + ); + context.raft_log.reset().await.expect("reset successfully!"); + test_utils::simulate_insert_command(&context.raft_log, vec![1], 1).await; + test_utils::simulate_insert_command(&context.raft_log, vec![2, 3], 2).await; + test_utils::simulate_insert_command(&context.raft_log, vec![4], 4).await; + + //case 1 + let l1 = Entry { + index: 2, + term: 2, + payload: None, + }; + let l2 = Entry { + index: 3, + term: 3, + payload: None, + }; + let new_ones = vec![l1, l2]; + let prev_log_index = 1; + let prev_log_term = 1; + let last_applied = context + .raft_log + .filter_out_conflicts_and_append(prev_log_index, prev_log_term, new_ones) + .await + .unwrap() + .unwrap(); + assert_eq!(last_applied.index, 3); + + let e = context.raft_log.entry(3).unwrap().unwrap(); + assert_eq!(e.term, 3); +} +#[tokio::test] +#[traced_test] +async fn test_filter_out_conflicts_and_append() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_filter_out_conflicts_and_append", + ); + // let state = &context.s; + context.raft_log.reset().await.expect("reset successfully!"); + test_utils::simulate_insert_command(&context.raft_log, vec![1], 1).await; + test_utils::simulate_insert_command(&context.raft_log, vec![2, 3], 2).await; + test_utils::simulate_insert_command(&context.raft_log, vec![4], 4).await; + + //case 1 + let l1 = Entry { + index: 2, + term: 2, + payload: None, + }; + let l2 = Entry { + index: 3, + term: 3, + payload: None, + }; + let new_ones = vec![l1, l2]; + let prev_log_index = 1; + let prev_log_term = 1; + let last_applied = context + .raft_log + .filter_out_conflicts_and_append(prev_log_index, prev_log_term, new_ones) + .await + .unwrap() + .unwrap(); + assert_eq!(last_applied.index, 3); + + let e = context.raft_log.entry(3).unwrap().unwrap(); + assert_eq!(e.term, 3); + + //case 2 + let l3 = Entry { + index: 4, + term: 2, + payload: None, + }; + let new_ones = vec![l3]; + let prev_log_index = 1; + let prev_log_term = 1; + let last_applied = context + .raft_log + .filter_out_conflicts_and_append(prev_log_index, prev_log_term, new_ones) + .await + .unwrap() + .unwrap(); + assert_eq!(last_applied.index, 4); + let e = context.raft_log.entry(4).unwrap().unwrap(); + assert_eq!(e.term, 2); +} + +#[tokio::test] +#[traced_test] +async fn test_get_last_raft_log() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_get_last_raft_log", + ); + + context.raft_log.reset().await.expect("should succeed"); + let mut vec_1 = Vec::new(); + for id in 1..300 { + vec_1.push(id); + } + test_utils::simulate_insert_command(&context.raft_log, vec_1, 1).await; + + let mut vec_2 = Vec::new(); + for id in 1..3 { + vec_2.push(id); + } + test_utils::simulate_insert_command(&context.raft_log, vec_2, 1).await; + if let Some(last) = context.raft_log.last_entry() { + assert_eq!(last.index, 301); + } +} + +#[tokio::test] +#[traced_test] +async fn test_sled_last() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_sled_last", + ); + + test_utils::simulate_insert_command(&context.raft_log, (1..=300).collect(), 1).await; + + let l = context.raft_log.last_entry().unwrap(); + let last = l.index; + let len = context.raft_log.len(); + debug!("last: {:?}, while len: {:?}", last, len); + assert_eq!(last, len as u64); +} +#[tokio::test] +#[traced_test] +async fn test_sled_last_max() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_sled_last_max", + ); + + let max = u64::MAX; //max of u64 + test_utils::simulate_insert_command(&context.raft_log, ((max - 1)..max).collect(), 1).await; + + let l = context.raft_log.last_entry().unwrap(); + assert_eq!(l.index, 1); +} + +///3 nodes, leader append 1000 logs, the other two logs can not get 1000 +/// entries +/// to test insert_one_client_command and get_entries_range two functions +#[tokio::test] +#[traced_test] +async fn test_insert_one_client_command() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_insert_one_client_command", + ); + + context.raft_log.reset().await.expect("reset successfully!"); + + let mut v = Vec::new(); + for i in 1..2001 { + v.push(i); + } + test_utils::simulate_insert_command(&context.raft_log, v, 1).await; + + let mut count = 900; + for entry in context.raft_log.get_entries_range(900..=2000).unwrap() { + assert_eq!(entry.index, count); + count += 1; + } +} + +#[tokio::test] +#[traced_test] +async fn test_get_raft_log_entry_between() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_get_raft_log_entry_between", + ); + + context.raft_log.reset().await.expect("reset successfully!"); + + let mut v = Vec::new(); + for i in 1..2001 { + v.push(i); + } + test_utils::simulate_insert_command(&context.raft_log, v, 1).await; + + let log_len = context.raft_log.last_entry_id(); + let result = context.raft_log.get_entries_range(900..=log_len).unwrap(); + let mut count = 900; + for entry in &result { + assert_eq!(entry.index, count); + count += 1; + } + assert_eq!(result.len(), 1101); + + let result = context.raft_log.get_entries_range(900..=log_len - 1).unwrap(); + assert_eq!(result.len(), 1100); + + //end index will not be returned as result + let result = context.raft_log.get_entries_range(0..=log_len).unwrap(); + assert_eq!(result.len(), log_len as usize); + + //index starts from 1 + let result = context.raft_log.get_entries_range(0..=0).unwrap(); + assert_eq!(result.len(), 0); + + //index starts from 1 + let result = context.raft_log.get_entries_range(0..=1).unwrap(); + assert_eq!(result.len(), 1); +} +/// Test duplicated insert +/// Leader might received duplicated commands from clients in several RPC +/// request. But, leader should not treat them as duplicated entries. They are +/// just sequence events. +#[tokio::test] +#[traced_test] +async fn test_insert_one_client_command_dup_case() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_insert_one_client_command_dup_case", + ); + + context.raft_log.reset().await.expect("reset successfully!"); + + let mut v = Vec::new(); + for i in 1..10 { + v.push(i); + } + test_utils::simulate_insert_command(&context.raft_log, v, 1).await; + + let len = context.raft_log.last_entry_id(); + let mut v = Vec::new(); + for i in 1..10 { + v.push(i + len); + } + test_utils::simulate_insert_command(&context.raft_log, v, 2).await; + + assert_eq!(context.raft_log.last_entry_id(), 18); + let last = context.raft_log.last_entry().unwrap(); + assert_eq!(last.index, 18); +} + +/// # Test client propose commands which contains insert and delete +/// +/// ## Criterias: +/// 1. all client proposal should be recorded in RaftLog without lose +#[tokio::test] +#[traced_test] +async fn test_client_proposal_insert_delete() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_client_proposal_insert_delete", + ); + + context.raft_log.reset().await.expect("reset successfully!"); + + test_utils::simulate_insert_command(&context.raft_log, (1..=10).collect(), 1).await; + assert_eq!(context.raft_log.last_entry_id(), 10); + + test_utils::simulate_delete_command(&context.raft_log, 1..=3, 1).await; + assert_eq!(context.raft_log.last_entry_id(), 13); + assert_eq!(context.raft_log.len(), 13); + + test_utils::simulate_insert_command(&context.raft_log, (11..=20).collect(), 1).await; + + assert_eq!(context.raft_log.last_entry_id(), 23); + assert_eq!(context.raft_log.len(), 23); +} + +///Bug: setup logger with error: SetGlobalDefaultError("a global default trace +/// dispatcher has already been set") +/// #[traced_test] +#[tokio::test] +#[traced_test] +async fn test_purge_logs_up_to() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_purge_logs_up_to", + ); + + context.raft_log.reset().await.expect("reset successfully!"); + + let mut entries = Vec::new(); + for i in 1..10 { + entries.push(Entry { + index: i, + term: 1, + payload: None, + }); + } + context.raft_log.insert_batch(entries).await.expect("should succeed"); + + //.. + // assume we have generated snapshot until $last_applied, + // now we can clean the local logs + let last_applied = LogId { index: 3, term: 1 }; + context.raft_log.purge_logs_up_to(last_applied).await.expect("should succeed"); + + assert_eq!(9, context.raft_log.last_entry_id()); + assert_eq!(None, context.raft_log.entry(3).unwrap()); + assert_eq!(None, context.raft_log.entry(2).unwrap()); + assert_eq!(None, context.raft_log.entry(1).unwrap()); +} + +#[tokio::test] +#[traced_test] +async fn test_purge_logs_up_to_concurrent_purge() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_purge_logs_up_to_concurrent_purge", + ); + context.raft_log.reset().await.expect("reset failed"); + + // Insert test logs + let entries: Vec<_> = (1..=10) + .map(|i| Entry { + index: i, + term: 1, + payload: None, + }) + .collect(); + context.raft_log.insert_batch(entries).await.unwrap(); + + // Concurrent purge calls + let handles: Vec<_> = vec![1, 2, 3, 4, 5, 6, 7] + .into_iter() + .map(|cutoff| { + let raft_log = context.raft_log.clone(); + tokio::spawn(async move { + raft_log + .purge_logs_up_to(LogId { + index: cutoff, + term: 1, + }) + .await + .unwrap(); + }) + }) + .collect(); + + // Wait for all threads + join_all(handles).await; + + // Verify final state + assert_eq!(10, context.raft_log.last_entry_id()); + + // The highest successful purge should win + assert!(context.raft_log.entry(7).unwrap().is_none()); + assert!(context.raft_log.entry(8).unwrap().is_some()); +} + +#[tokio::test] +#[traced_test] +async fn test_get_first_raft_log_entry_id_after_delete_entries() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_get_first_raft_log_entry_id_after_delete_entries", + ); + + context.raft_log.reset().await.expect("reset successfully!"); + let mut entries = Vec::new(); + for i in 1..=10 { + entries.push(Entry { + index: i, + term: 1, + payload: None, + }); + } + context.raft_log.insert_batch(entries).await.expect("should succeed"); + + let last_applied = LogId { index: 4, term: 1 }; + context.raft_log.purge_logs_up_to(last_applied).await.expect("should succeed"); + assert_eq!(10, context.raft_log.last_entry_id()); + assert_eq!(None, context.raft_log.entry(3).unwrap()); + assert_eq!(None, context.raft_log.entry(2).unwrap()); + assert_eq!(None, context.raft_log.entry(1).unwrap()); + + assert_eq!(context.raft_log.first_entry_id(), 5); +} + +/// Case 1: we have multi thread working concurrently +/// each thread should get unique pre allocated index for pending apply +/// entries At last when we retrieve last entry of the locallog, we should +/// see the last pre allocated index +#[tokio::test] +#[traced_test] +async fn test_pre_allocate_raft_logs_next_index_case1() { + let context = TestContext::new( + PersistenceStrategy::DiskFirst, + FlushPolicy::Immediate, + "test_pre_allocate_raft_logs_next_index_case1", + ); + + context.raft_log.reset().await.expect("reset successfully!"); + let start = context.raft_log.last_entry_id(); + println!("start: {start}",); + + let mut handles = Vec::new(); + + let i = 10; + let j = 10; + for _ in 1..=i { + let cloned_raft_log = context.raft_log.clone(); + let handle = tokio::spawn(async move { + let mut entries: Vec = Vec::new(); + for i in 1..=j { + entries.push(Entry { + index: cloned_raft_log.pre_allocate_raft_logs_next_index(), + term: 1, + payload: Some(EntryPayload::command(generate_insert_commands(vec![i]))), + }); + } + cloned_raft_log.insert_batch(entries.clone()).await.expect("should succeed"); + }); + handles.push(handle); + } + let _results = join_all(handles).await; + let end = context.raft_log.last_entry_id(); + println!("end:{end}",); + + assert_eq!(start + i * j, end); +} + +/// Case 2: we have mutil thread working concurrently +/// there might be one thread failed to apply the local log entries +/// which cases some log entry index might not really exists inside local +/// logs we still want to validate the last log entry id is the same one as +/// we expected #[ignore = "architecture changes, this case will not exist"] +#[tokio::test] +#[traced_test] +async fn test_pre_allocate_raft_logs_next_index_case2() { + let context = TestContext::new( + PersistenceStrategy::DiskFirst, + FlushPolicy::Immediate, + "test_pre_allocate_raft_logs_next_index_case2", + ); + + context.raft_log.reset().await.expect("reset successfully!"); + let start = context.raft_log.last_entry_id(); + println!("start: {start}",); + + let mut handles = Vec::new(); + + let i = 10; + let k = 3; //make sure k = Vec::new(); + for i in 1..=j { + entries.push(Entry { + index: cloned_raft_log.pre_allocate_raft_logs_next_index(), + term: 1, + payload: Some(EntryPayload::command(generate_insert_commands(vec![i]))), + }); + } + }); + handles.push(handle); + } + + for _ in k + 1..=i { + let cloned_raft_log = context.raft_log.clone(); + let handle = tokio::spawn(async move { + let mut entries: Vec = Vec::new(); + for i in 1..=j { + entries.push(Entry { + index: cloned_raft_log.pre_allocate_raft_logs_next_index(), + term: 1, + payload: Some(EntryPayload::command(generate_insert_commands(vec![i]))), + }); + } + cloned_raft_log.insert_batch(entries.clone()).await.expect("should succeed"); + }); + handles.push(handle); + } + let _results = join_all(handles).await; + let end = context.raft_log.last_entry_id(); + println!("end:{end}",); + + assert_eq!(start + i * j, end); +} + +/// Case 1: stress testing with 100K entries, +/// the entries was insert successfully and last log entry id is as expected +#[ignore = "architecture changes, this case will not exist"] +#[tokio::test] +#[traced_test] +async fn test_insert_batch_logs_case1() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Immediate, + "test_insert_batch_logs_case1", + ); + + context.raft_log.reset().await.expect("reset successfully!"); + let start = context.raft_log.last_entry_id(); + println!("start: {start}",); + + let mut handles = Vec::new(); + + let i = 1000; + let j = 100; + + for _ in 1..=i { + let cloned_raft_log = context.raft_log.clone(); + let handle = tokio::spawn(async move { + let mut entries: Vec = Vec::new(); + for i in 1..=j { + entries.push(Entry { + index: cloned_raft_log.pre_allocate_raft_logs_next_index(), + term: 1, + payload: Some(EntryPayload::command(generate_insert_commands(vec![i]))), + }); + } + cloned_raft_log.insert_batch(entries.clone()).await.expect("should succeed"); + }); + handles.push(handle); + } + let _results = join_all(handles).await; + let end = context.raft_log.last_entry_id(); + println!("end:{end}",); + + assert_eq!(start + i * j, end); +} + +fn give_me_mock_storage() -> Arc { + // let mut mock_log_store = MockLogStore::new(); + // mock_log_store.expect_last_index().returning(|| 0); + // mock_log_store.expect_persist_entries().returning(|_| Ok(())); + // mock_log_store.expect_entry().returning(|_| Ok(None)); + // mock_log_store.expect_get_entries().returning(|_| Ok(vec![])); + // mock_log_store.expect_purge().returning(|_| Ok(())); + // mock_log_store.expect_truncate().returning(|_| Ok(())); + // mock_log_store.expect_flush().returning(|| Ok(())); + // mock_log_store.expect_flush_async().returning(|| Ok(())); + // mock_log_store.expect_reset().returning(|| Ok(())); + + // let mut mock_meta_store = MockMetaStore::new(); + // mock_meta_store.expect_save_hard_state().returning(|_| Ok(())); + // mock_meta_store.expect_load_hard_state().returning(|| Ok(None)); + // mock_log_store.expect_flush().returning(|| Ok(())); + // mock_log_store.expect_flush_async().returning(|| Ok(())); + + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path().to_path_buf(); + Arc::new(FileStorageEngine::new(path).unwrap()) +} + +/// # Case 2: Test scenario for log replication conflict handling when combining: +/// 1. `filter_out_conflicts_and_append` (follower's perspective) +/// 2. `purge_logs_up_to` (snapshot compaction) +/// 3. `insert_batch` (leader's log appending) +/// +/// # Test Scenario Flow +/// +/// 1. **Setup Phase**: +/// - Old leader (T1) appends logs 1-10 (term 1) +/// - Generates snapshot up to index 3 (compaction) +/// +/// 2. **Leadership Change**: +/// - Old leader restarts and becomes follower +/// - New leader (T2) elected with logs 1-8 (term 1) +/// +/// 3. **Conflict Handling**: +/// - New leader appends logs 9-10 (term 2) +/// - Old follower processes AppendEntries RPC +/// +/// # Validation Points +/// - Final log state must have last entry index=10, term=2 +/// - Log continuity: index 7 (term 1) and 8 (term 2) must coexist +#[tokio::test] +#[traced_test] +async fn test_insert_batch_logs_case2() { + // 1. Initialize two nodes (old_leader and new_leader) + let (old_leader, receiver) = + BufferedRaftLog::>::new( + 1, + PersistenceConfig { + strategy: PersistenceStrategy::DiskFirst, + flush_policy: FlushPolicy::Immediate, + max_buffered_entries: 1000, + ..Default::default() + }, + give_me_mock_storage(), + ); + let old_leader = old_leader.start(receiver); + + let (new_leader, receiver) = + BufferedRaftLog::>::new( + 2, + PersistenceConfig { + strategy: PersistenceStrategy::DiskFirst, + flush_policy: FlushPolicy::Immediate, + max_buffered_entries: 1000, + ..Default::default() + }, + give_me_mock_storage(), + ); + let new_leader = new_leader.start(receiver); + + let mut entries: Vec = Vec::new(); + for i in 1..=7 { + entries.push(Entry { + index: old_leader.pre_allocate_raft_logs_next_index(), + term: 1, + payload: Some(EntryPayload::command(generate_insert_commands(vec![i]))), + }); + } + + // 2. Old leader initial logs (term 1) + old_leader.insert_batch(entries.clone()).await.expect("should succeed"); + new_leader.insert_batch(entries.clone()).await.expect("should succeed"); + + // 3. Add uncommitted entry on old leader + old_leader + .insert_batch(vec![Entry { + index: old_leader.pre_allocate_raft_logs_next_index(), + term: 1, + payload: Some(EntryPayload::command(generate_insert_commands(vec![8]))), + }]) + .await + .expect("should succeed"); + + // 4. Simulate snapshot compaction + let last_applied = LogId { index: 3, term: 1 }; + old_leader.purge_logs_up_to(last_applied).await.expect("should succeed"); + + // 5. New leader appends higher term logs + let mut handles = Vec::new(); + let mut new_leader_entries: Vec = Vec::new(); + for i in 8..=10 { + new_leader_entries.push(Entry { + index: new_leader.pre_allocate_raft_logs_next_index(), + term: 2, + payload: Some(EntryPayload::command(generate_insert_commands(vec![i]))), + }); + } + let cloned_new_leader_entries = new_leader_entries.clone(); + let handle = tokio::spawn(async move { + new_leader + .insert_batch(cloned_new_leader_entries.clone()) + .await + .expect("should succeed"); + }); + handles.push(handle); + let _results = join_all(handles).await; + + debug!("new_leader_entries: {:?}", new_leader_entries.clone()); + + // 6. Follower processes conflicting entries + let ex_leader_last_appled_id = old_leader + .filter_out_conflicts_and_append(8, 1, new_leader_entries.clone()) + .await + .unwrap() + .unwrap(); + assert_eq!(ex_leader_last_appled_id.index, 10); + + // // 7. Validate final log state + // if let Some(ex_leader_last_entry) = old_leader.last() { + // assert_eq!(ex_leader_last_entry.index, 10); + // assert_eq!(ex_leader_last_entry.term, 2); + // } + + // if let Some(entry) = old_leader.entry(7) { + // assert_eq!(entry.term, 1); + // } + // if let Some(entry) = old_leader.entry(8) { + // assert_eq!(entry.term, 2); + // } + + // 7. Validate final log state + validate_log_continuity( + &old_leader, + &[ + (7, 1), // Original term 1 entry + (8, 2), // Overwritten entry + (10, 2), // New highest entry + ], + ) + .await; +} + +/// Helper function: Validate log entries' term continuity +async fn validate_log_continuity( + node: &Arc>>, + expected: &[(u64, u64)], +) { + for (index, term) in expected { + let entry = node + .entry(*index) + .unwrap() + .unwrap_or_else(|| panic!("Missing entry at index {index}",)); + + assert_eq!( + entry.term, *term, + "Term mismatch at index {}: expected {}, got {}", + index, term, entry.term + ); + } +} + +#[tokio::test] +#[traced_test] +async fn test_apply_and_then_get_last() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_apply_and_then_get_last", + ); + + context.raft_log.reset().await.expect("reset successfully!"); + test_utils::simulate_insert_command(&context.raft_log, vec![1, 2], 1).await; + assert_eq!(2, context.raft_log.last_entry_id()); +} + +#[tokio::test] +#[traced_test] +async fn test_pre_allocate_raft_logs_next_index() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Immediate, + "test_pre_allocate_raft_logs_next_index", + ); + + assert_eq!(1, context.raft_log.pre_allocate_raft_logs_next_index()); + assert_eq!(2, context.raft_log.pre_allocate_raft_logs_next_index()); +} + +#[tokio::test] +#[traced_test] +async fn test_calculate_majority_matched_index_case0() { + let c = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_calculate_majority_matched_index_case0", + ); + let raft_log = c.raft_log.clone(); + raft_log.reset().await.expect("reset successfully!"); + + let current_term = 2; + let commit_index = 2; + + test_utils::simulate_insert_command(&raft_log, vec![1], 1).await; + test_utils::simulate_insert_command(&raft_log, vec![2, 3], 2).await; + + assert_eq!( + Some(3), + raft_log.calculate_majority_matched_index(current_term, commit_index, vec![2, 12]) + ); +} + +/// If there exists an N such that N > commitIndex, a majority +/// of matchIndex[i] ≥ N, and log[N].term == currentTerm: set commitIndex = N +/// (§5.3, §5.4). +#[tokio::test] +#[traced_test] +async fn test_calculate_majority_matched_index_case1() { + let c = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_calculate_majority_matched_index_case1", + ); + let raft_log = c.raft_log.clone(); + let _ = c.raft_log.reset().await; + //case 1: majority matched index is 2, commit_index: 4, current_term is 3, + // while log(2) term is 2, return None + let ct = 3; + let ci = 4; + + test_utils::simulate_insert_command(&raft_log, vec![1], 1).await; + test_utils::simulate_insert_command(&raft_log, vec![2], 2).await; + assert_eq!( + None, + raft_log.calculate_majority_matched_index(ct, ci, vec![1, 2]) + ); +} + +#[tokio::test] +#[traced_test] +async fn test_calculate_majority_matched_index_case2() { + let c = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_calculate_majority_matched_index_case2", + ); + let raft_log = c.raft_log.clone(); + let _ = c.raft_log.reset().await; + + //case 2: majority matched index is 3, commit_index: 2, current_term is 3, + // while log(3) term is 3, return Some(3) + let ct = 3; + let ci = 2; + + test_utils::simulate_insert_command(&raft_log, vec![1], 1).await; + test_utils::simulate_insert_command(&raft_log, vec![2], 2).await; + test_utils::simulate_insert_command(&raft_log, vec![3], 3).await; + assert_eq!( + Some(3), + raft_log.calculate_majority_matched_index(ct, ci, vec![4, 2]) + ); +} + +#[tokio::test] +#[traced_test] +async fn test_calculate_majority_matched_index_case3() { + let c = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_calculate_majority_matched_index_case3", + ); + let raft_log = c.raft_log.clone(); + + //case 3: majority matched index is 3, commit_index: 2, current_term is 3, + // while log(3) term is 2, return None + let ct = 3; + let ci = 2; + test_utils::simulate_insert_command(&raft_log, vec![1], 1).await; + test_utils::simulate_insert_command(&raft_log, vec![2], 2).await; + + assert_eq!( + None, + raft_log.calculate_majority_matched_index(ct, ci, vec![3, 2]) + ); +} + +#[tokio::test] +#[traced_test] +async fn test_calculate_majority_matched_index_case4() { + let c = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_calculate_majority_matched_index_case4", + ); + + let raft_log = c.raft_log.clone(); + + //case 3: majority matched index is 3, commit_index: 2, current_term is 3, + // while log(3) term is 2, return None + let ct = 3; + let ci = 2; + + test_utils::simulate_insert_command(&raft_log, vec![1], 1).await; + test_utils::simulate_insert_command(&raft_log, vec![2], 2).await; + + assert_eq!( + None, + raft_log.calculate_majority_matched_index(ct, ci, vec![2, 2]) + ); +} + +/// # Case 5: stress testing +/// +/// ## Setup +/// 1. Simulate 100,000 local log entries +/// 2. peer1's match: 90,000, peer2's match 98,000 +/// +/// ## Ceriteria: +/// 1. compare calculate_majority_matched_index as calculate_majority_matched_index2's performance +#[tokio::test] +#[traced_test] +async fn test_calculate_majority_matched_index_case5() { + let c = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_calculate_majority_matched_index_case5", + ); + let raft_log = c.raft_log.clone(); + + let term = 1; + let raft_log_length = 100000; + let commit = 90000; + + let peer1_match = 97000; + let peer2_match = 98000; + + let raft_log_entry_ids: Vec = (1..=raft_log_length).collect(); + test_utils::simulate_insert_command(&raft_log, raft_log_entry_ids, 1).await; + + let now = Instant::now(); + assert_eq!( + Some(peer2_match), + raft_log.calculate_majority_matched_index(term, commit, vec![peer1_match, peer2_match]) + ); + println!( + "calculate_majority_matched_index takes: {} micros.", + now.elapsed().as_micros() + ); +} + +#[tokio::test] +#[traced_test] +async fn test_raft_log_insert() { + let c = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_raft_log_insert", + ); + let raft_log = c.raft_log.clone(); + + test_utils::simulate_insert_command(&raft_log, vec![1], 1).await; + assert_eq!(1, raft_log.last_entry_id()); +} + +/// #Case 1: local raft log is empty +#[tokio::test] +#[traced_test] +async fn test_is_empty_case1() { + let c = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Immediate, + "test_is_empty_case1", + ); + let raft_log = c.raft_log.clone(); + + assert!(raft_log.is_empty()); +} + +/// #Case 2: local raft log is not empty +#[tokio::test] +#[traced_test] +async fn test_is_empty_case2() { + let c = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_is_empty_case2", + ); + let raft_log = c.raft_log.clone(); + test_utils::simulate_insert_command(&raft_log, vec![1], 1).await; + + assert!(!raft_log.is_empty()); +} + +/// # Case1: No last log in raft_log, should returns (0,0) +#[tokio::test] +#[traced_test] +async fn test_get_last_entry_metadata_case1() { + let c = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Immediate, + "test_get_last_entry_metadata_case1", + ); + let raft_log = c.raft_log.clone(); + + assert_eq!( + LogId { index: 0, term: 0 }, + raft_log.last_log_id().unwrap_or(LogId { index: 0, term: 0 }) + ); +} +/// # Case2: There is last log in raft_log, should returns last log metadata +#[tokio::test] +#[traced_test] +async fn test_get_last_entry_metadata_case2() { + let c = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_get_last_entry_metadata_case2", + ); + let raft_log = c.raft_log.clone(); + test_utils::simulate_insert_command(&raft_log, vec![1], 11).await; + + assert_eq!( + LogId { index: 1, term: 11 }, + raft_log.last_log_id().unwrap_or(LogId { index: 0, term: 0 }) + ); +} + +#[tokio::test] +#[traced_test] +async fn test_raft_log_drop() { + // Save the instance ID from the first storage + + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path().to_path_buf(); + let storage = FileStorageEngine::new(path.clone()).unwrap(); + + { + let (raft_log, receiver) = + BufferedRaftLog::>::new( + 1, + PersistenceConfig { + strategy: PersistenceStrategy::DiskFirst, + flush_policy: FlushPolicy::Immediate, + max_buffered_entries: 1000, + ..Default::default() + }, + Arc::new(storage), + ); + let raft_log = raft_log.start(receiver); + test_utils::simulate_insert_command(&raft_log, vec![1], 1).await; + drop(raft_log); + } + + // Reuse the same instance ID to access persisted entries + let storage = FileStorageEngine::new(path.clone()).unwrap(); + assert!(!storage.log_store().get_entries(0..=1).unwrap().is_empty()); +} + +#[tokio::test] +#[traced_test] +async fn test_first_index_for_term() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_first_index_for_term", + ); + context.raft_log.reset().await.expect("reset successfully!"); + + // Insert test logs with various terms + let entries = vec![ + Entry { + index: 1, + term: 1, + payload: None, + }, + Entry { + index: 2, + term: 1, + payload: None, + }, + Entry { + index: 3, + term: 2, + payload: None, + }, + Entry { + index: 4, + term: 2, + payload: None, + }, + Entry { + index: 5, + term: 3, + payload: None, + }, + Entry { + index: 6, + term: 3, + payload: None, + }, + Entry { + index: 7, + term: 3, + payload: None, + }, + ]; + context.raft_log.insert_batch(entries).await.expect("insert should succeed"); + + // Test various cases + assert_eq!(context.raft_log.first_index_for_term(1), Some(1)); + assert_eq!(context.raft_log.first_index_for_term(2), Some(3)); + assert_eq!(context.raft_log.first_index_for_term(3), Some(5)); + assert_eq!(context.raft_log.first_index_for_term(4), None); // Term not found + + // Test with partial data + let entries = vec![ + Entry { + index: 8, + term: 4, + payload: None, + }, + Entry { + index: 9, + term: 5, + payload: None, + }, + ]; + context.raft_log.insert_batch(entries).await.expect("insert should succeed"); + assert_eq!(context.raft_log.first_index_for_term(4), Some(8)); + assert_eq!(context.raft_log.first_index_for_term(5), Some(9)); + + // Test with empty log + context.raft_log.reset().await.expect("reset should succeed"); + assert_eq!(context.raft_log.first_index_for_term(1), None); +} + +#[tokio::test] +#[traced_test] +async fn test_last_index_for_term() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_last_index_for_term", + ); + context.raft_log.reset().await.expect("reset successfully!"); + + // Insert test logs with various terms + let entries = vec![ + Entry { + index: 1, + term: 1, + payload: None, + }, + Entry { + index: 2, + term: 1, + payload: None, + }, + Entry { + index: 3, + term: 2, + payload: None, + }, + Entry { + index: 4, + term: 2, + payload: None, + }, + Entry { + index: 5, + term: 3, + payload: None, + }, + Entry { + index: 6, + term: 3, + payload: None, + }, + Entry { + index: 7, + term: 3, + payload: None, + }, + ]; + context.raft_log.insert_batch(entries).await.expect("insert should succeed"); + + // Test various cases + assert_eq!(context.raft_log.last_index_for_term(1), Some(2)); + assert_eq!(context.raft_log.last_index_for_term(2), Some(4)); + assert_eq!(context.raft_log.last_index_for_term(3), Some(7)); + assert_eq!(context.raft_log.last_index_for_term(4), None); // Term not found + + // Test with gaps in terms + let entries = vec![ + Entry { + index: 8, + term: 5, + payload: None, + }, + Entry { + index: 9, + term: 5, + payload: None, + }, + ]; + context.raft_log.insert_batch(entries).await.expect("insert should succeed"); + assert_eq!(context.raft_log.last_index_for_term(5), Some(9)); + + // Test with empty log + context.raft_log.reset().await.expect("reset should succeed"); + assert_eq!(context.raft_log.last_index_for_term(1), None); +} + +#[tokio::test] +#[traced_test] +async fn test_term_index_functions_with_purged_logs() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_term_index_functions_with_purged_logs", + ); + context.raft_log.reset().await.expect("reset successfully!"); + + // Insert initial logs + let entries: Vec<_> = (1..=10) + .map(|i| Entry { + index: i, + term: if i <= 3 { 1 } else { 2 }, + payload: None, + }) + .collect(); + context.raft_log.insert_batch(entries).await.expect("insert should succeed"); + + // Purge first 5 logs + context + .raft_log + .purge_logs_up_to(LogId { index: 5, term: 2 }) + .await + .expect("purge should succeed"); + + // Verify after purge + assert_eq!(context.raft_log.first_index_for_term(1), None); // Term 1 purged + assert_eq!(context.raft_log.last_index_for_term(1), None); // Term 1 purged + assert_eq!(context.raft_log.first_index_for_term(2), Some(6)); + assert_eq!(context.raft_log.last_index_for_term(2), Some(10)); +} + +#[tokio::test] +#[traced_test] +async fn test_term_index_functions_with_concurrent_writes() { + let (raft_log, log_command_receiver) = + BufferedRaftLog::>::new( + 1, + PersistenceConfig { + strategy: PersistenceStrategy::MemFirst, + flush_policy: FlushPolicy::Immediate, + max_buffered_entries: 1000, + ..Default::default() + }, + give_me_mock_storage(), + ); + let raft_log = raft_log.start(log_command_receiver); + // raft_log.reset().await.expect("reset successfully!"); + + // Add a small delay to ensure command processor is ready + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + + let mut handles = Vec::new(); + + for i in 0..10 { + let cloned_raft_log = raft_log.clone(); + let handle = tokio::spawn(async move { + let term = i + 1; + for j in 1..=100 { + let index = (i * 100) + j; + let entry = Entry { + index, + term, + payload: None, + }; + cloned_raft_log.insert_batch(vec![entry]).await.expect("insert should succeed"); + } + }); + handles.push(handle); + } + + for handle in handles { + handle.await.expect("task panicked"); + println!("Task completed"); + } + + // Small delay to ensure all persist commands are processed + tokio::time::sleep(tokio::time::Duration::from_millis(50)).await; + + // Verify term indices + for term in 1..=10 { + assert_eq!( + raft_log.first_index_for_term(term), + Some((term - 1) * 100 + 1) + ); + assert_eq!(raft_log.last_index_for_term(term), Some(term * 100)); + } +} + +#[tokio::test] +#[traced_test] +async fn test_term_index_performance_large_dataset() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_term_index_performance_large_dataset", + ); + context.raft_log.reset().await.expect("reset successfully!"); + + // Insert 100,000 logs with terms cycling every 100 logs + let mut entries = Vec::new(); + for i in 1..=100_000 { + let term = i / 100 + 1; + entries.push(Entry { + index: i, + term, + payload: None, + }); + } + context.raft_log.insert_batch(entries).await.expect("insert should succeed"); + + // Measure performance for first_index_for_term + let start = std::time::Instant::now(); + assert_eq!(context.raft_log.first_index_for_term(1), Some(1)); + assert_eq!(context.raft_log.first_index_for_term(500), Some(49900)); + assert_eq!(context.raft_log.first_index_for_term(1000), Some(99900)); + println!("first_index_for_term took: {:?}", start.elapsed()); + + // Measure performance for last_index_for_term + let start = std::time::Instant::now(); + assert_eq!(context.raft_log.last_index_for_term(1), Some(99)); + assert_eq!(context.raft_log.last_index_for_term(500), Some(49999)); + assert_eq!(context.raft_log.last_index_for_term(1000), Some(99999)); + println!("last_index_for_term took: {:?}", start.elapsed()); +} +#[cfg(test)] +mod id_allocation_tests { + use std::collections::HashSet; + use std::ops::RangeInclusive; + use std::sync::Arc; + use std::sync::atomic::Ordering; + + use super::*; + use crate::FileStorageEngine; + + // In-memory test setup + fn setup_memory() -> Arc>> { + let (raft_log, _receiver) = + BufferedRaftLog::>::new( + 1, + PersistenceConfig { + strategy: PersistenceStrategy::MemFirst, + flush_policy: FlushPolicy::Immediate, + max_buffered_entries: 1000, + ..Default::default() + }, + give_me_mock_storage(), + ); + + Arc::new(raft_log) + } + + // Helper to check empty ranges + fn is_empty_range(range: &RangeInclusive) -> bool { + range.start() == &u64::MAX && range.end() == &u64::MAX + } + + #[tokio::test] + async fn test_pre_allocate_next_index_sequential() { + let raft_log = setup_memory(); + + assert_eq!(raft_log.pre_allocate_raft_logs_next_index(), 1); + assert_eq!(raft_log.pre_allocate_raft_logs_next_index(), 2); + assert_eq!(raft_log.pre_allocate_raft_logs_next_index(), 3); + + assert_eq!(raft_log.next_id.load(Ordering::SeqCst), 4); + } + + #[tokio::test] + async fn test_pre_allocate_id_range_exact_batch() { + let raft_log = setup_memory(); + + let range = raft_log.pre_allocate_id_range(100); + assert_eq!(*range.start(), 1); + assert_eq!(*range.end(), 100); + + assert_eq!(raft_log.pre_allocate_raft_logs_next_index(), 101); + } + + #[tokio::test] + async fn test_pre_allocate_id_range_zero_count() { + let raft_log = setup_memory(); + let initial_id = raft_log.next_id.load(Ordering::SeqCst); + + let range = raft_log.pre_allocate_id_range(0); + assert!(is_empty_range(&range)); + assert_eq!(raft_log.next_id.load(Ordering::SeqCst), initial_id); + } + + #[tokio::test] + async fn test_pre_allocate_id_range_after_zero() { + let raft_log = setup_memory(); + + // Allocate 0 (should be empty) + let empty_range = raft_log.pre_allocate_id_range(0); + assert!(is_empty_range(&empty_range)); + + // Valid allocation should work after empty + let range = raft_log.pre_allocate_id_range(5); + assert_eq!(*range.start(), 1); + assert_eq!(*range.end(), 5); + } + + #[tokio::test] + async fn test_pre_allocate_id_range_concurrent() { + let raft_log = setup_memory(); + let raft_log_clone = Arc::clone(&raft_log); + + let handle = std::thread::spawn(move || raft_log_clone.pre_allocate_id_range(50)); + + let main_range = raft_log.pre_allocate_id_range(30); + let thread_range = handle.join().unwrap(); + + // Verify range size (no null check required) + let main_count = main_range.end() - main_range.start() + 1; + let thread_count = thread_range.end() - thread_range.start() + 1; + assert_eq!(main_count, 30); + assert_eq!(thread_count, 50); + + // Get all range endpoints + let points = [ + *main_range.start(), + *main_range.end(), + *thread_range.start(), + *thread_range.end(), + ]; + + let min_id = *points.iter().min().unwrap(); + let max_id = *points.iter().max().unwrap(); + + // Verify continuity: total number of IDs = (max_id - min_id + 1) + assert_eq!(main_count + thread_count, max_id - min_id + 1); + + // Verify final status + assert_eq!(raft_log.next_id.load(Ordering::SeqCst), max_id + 1); + } + + // Define result enumeration inside the test module + #[derive(Debug)] + enum AllocationResult { + Single(u64), + Range(RangeInclusive), + } + + #[tokio::test] + async fn test_concurrent_mixed_allocations() { + let raft_log = setup_memory(); + let mut handles = vec![]; + + // Start multiple threads for mixed allocation + for _ in 0..10 { + let log_clone = Arc::clone(&raft_log); + handles.push(std::thread::spawn(move || { + // Single ID allocation + let id = log_clone.pre_allocate_raft_logs_next_index(); + AllocationResult::Single(id) + })); + } + + for _ in 0..5 { + let log_clone = Arc::clone(&raft_log); + handles.push(std::thread::spawn(move || { + // range allocation + let range = log_clone.pre_allocate_id_range(10); + AllocationResult::Range(range) + })); + } + + // collect results + let mut results = vec![]; + for handle in handles { + results.push(handle.join().unwrap()); + } + + // Verify there are no duplicate IDs + let mut all_ids = HashSet::new(); + for result in &results { + match result { + AllocationResult::Single(id) => { + assert!(!all_ids.contains(id), "Duplicate ID: {id}",); + all_ids.insert(*id); + } + AllocationResult::Range(range) => { + if !range.is_empty() { + for id in *range.start()..=*range.end() { + assert!(!all_ids.contains(&id), "Duplicate ID: {id}",); + all_ids.insert(id); + } + } + } + } + } + + // Get the minimum and maximum IDs + let min_id = *all_ids.iter().min().unwrap_or(&0); + let max_id = *all_ids.iter().max().unwrap_or(&0); + + // Verify continuity and total count + if !all_ids.is_empty() { + assert_eq!( + all_ids.len() as u64, + max_id - min_id + 1, + "IDs are not contiguous: min={}, max={}, count={}, expected_count={}", + min_id, + max_id, + all_ids.len(), + max_id - min_id + 1 + ); + + // Verify final status + assert_eq!( + raft_log.next_id.load(Ordering::SeqCst), + max_id + 1, + "Next ID mismatch: expected {}, actual {}", + max_id + 1, + raft_log.next_id.load(Ordering::SeqCst) + ); + } + } + + #[tokio::test] + async fn test_pre_allocate_id_range_multiple_batches() { + let raft_log = setup_memory(); + + // Valid batches + let range1 = raft_log.pre_allocate_id_range(150); + let range2 = raft_log.pre_allocate_id_range(50); + + // Empty batch + let empty_range = raft_log.pre_allocate_id_range(0); + + // Another valid batch + let range3 = raft_log.pre_allocate_id_range(100); + + assert_eq!(*range1.start(), 1); + assert_eq!(*range1.end(), 150); + + assert_eq!(*range2.start(), 151); + assert_eq!(*range2.end(), 200); + + assert!(is_empty_range(&empty_range)); + + assert_eq!(*range3.start(), 201); + assert_eq!(*range3.end(), 300); + } + + #[tokio::test] + async fn test_mixed_allocation_strategies() { + let raft_log = setup_memory(); + + // Single allocation + assert_eq!(raft_log.pre_allocate_raft_logs_next_index(), 1); + + // Empty range + let empty = raft_log.pre_allocate_id_range(0); + assert!(is_empty_range(&empty)); + + // Range allocation + let range = raft_log.pre_allocate_id_range(5); + assert_eq!(*range.start(), 2); + assert_eq!(*range.end(), 6); + + // More single allocations + assert_eq!(raft_log.pre_allocate_raft_logs_next_index(), 7); + assert_eq!(raft_log.pre_allocate_raft_logs_next_index(), 8); + + // Large range allocation + let range = raft_log.pre_allocate_id_range(250); + assert_eq!(*range.start(), 9); + assert_eq!(*range.end(), 258); + + // Final check + assert_eq!(raft_log.pre_allocate_raft_logs_next_index(), 259); + } + + #[tokio::test] + async fn test_edge_cases() { + let raft_log = setup_memory(); + + // Single ID + let single = raft_log.pre_allocate_id_range(1); + assert_eq!(*single.start(), 1); + assert_eq!(*single.end(), 1); + + // Maximum u64 (theoretical, not recommended in practice) + raft_log.next_id.store(u64::MAX - 5, Ordering::SeqCst); + let range = raft_log.pre_allocate_id_range(5); + assert_eq!(*range.start(), u64::MAX - 5); + assert_eq!(*range.end(), u64::MAX - 1); + } +} + +// Strategy-specific test module +mod disk_first_tests { + + use super::*; + + #[tokio::test] + async fn test_basic_write_and_read() { + let ctx = TestContext::new( + PersistenceStrategy::DiskFirst, + FlushPolicy::Immediate, + "test_basic_write_and_read", + ); + ctx.append_entries(1, 5, 1).await; + + // Verify entries are immediately durable + assert_eq!(ctx.raft_log.durable_index.load(Ordering::Acquire), 5); + assert_eq!(ctx.raft_log.entry(3).unwrap().unwrap().index, 3); + } + + #[tokio::test] + async fn test_crash_recovery() { + // Create and populate storage + let original_ctx = TestContext::new( + PersistenceStrategy::DiskFirst, + FlushPolicy::Immediate, + "test_crash_recovery", + ); + + // Append an entry + original_ctx + .raft_log + .append_entries(vec![Entry { + index: 1, + term: 1, + payload: Some(EntryPayload::command(Bytes::from(b"data".to_vec()))), + }]) + .await + .unwrap(); + + // Ensure the entry is persisted for DiskFirst strategy + original_ctx.raft_log.flush().await.unwrap(); + + // Recover from the same storage (simulating restart) + let recovered_ctx = original_ctx.recover_from_crash(); + + // Simulate crash by dropping the original context + drop(original_ctx); + + sleep(Duration::from_millis(50)).await; // Allow recovery + + // Verify recovery - for DiskFirst, entries should be immediately durable + assert_eq!( + recovered_ctx.raft_log.durable_index.load(Ordering::Acquire), + 1 + ); + + // The entry should be available + let entry = recovered_ctx.raft_log.entry(1).unwrap(); + assert!(entry.is_some()); + assert_eq!(entry.unwrap().index, 1); + } + + // Add this test to verify the fix works correctly + #[tokio::test] + async fn test_crash_recovery_with_multiple_entries() { + // Create and populate storage + let original_ctx = TestContext::new( + PersistenceStrategy::DiskFirst, + FlushPolicy::Immediate, + "test_crash_recovery_with_multiple_entries", + ); + + // Append multiple entries + for i in 1..=5 { + original_ctx + .raft_log + .append_entries(vec![Entry { + index: i, + term: 1, + payload: Some(EntryPayload::command(Bytes::from( + format!("data{i}",).into_bytes(), + ))), + }]) + .await + .unwrap(); + } + + // Ensure all entries are persisted for DiskFirst strategy + original_ctx.raft_log.flush().await.unwrap(); + + // Verify all entries are in memory and durable + assert_eq!( + original_ctx.raft_log.durable_index.load(Ordering::Acquire), + 5 + ); + assert_eq!(original_ctx.raft_log.entries.len(), 5); + + // Recover from the same storage (simulating restart) + let recovered_ctx = original_ctx.recover_from_crash(); + + // Simulate crash by dropping the original context + drop(original_ctx); + + sleep(Duration::from_millis(50)).await; // Allow recovery + + // Verify recovery - all entries should be recovered + assert_eq!( + recovered_ctx.raft_log.durable_index.load(Ordering::Acquire), + 5 + ); + assert_eq!(recovered_ctx.raft_log.entries.len(), 5); + + // All entries should be available + for i in 1..=5 { + let entry = recovered_ctx.raft_log.entry(i).unwrap(); + assert!(entry.is_some()); + assert_eq!(entry.unwrap().index, i); + } + } + + // Test for MemFirst strategy crash recovery (should lose unflushed data) + #[tokio::test] + async fn test_crash_recovery_mem_first() { + // Create and populate storage with MemFirst strategy + let original_ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 10, + interval_ms: 1000, + }, + "test_crash_recovery_mem_first", + ); + + // Append entries but don't flush (simulate data in memory only) + for i in 1..=3 { + original_ctx + .raft_log + .append_entries(vec![Entry { + index: i, + term: 1, + payload: Some(EntryPayload::command(Bytes::from( + format!("data{i}",).into_bytes(), + ))), + }]) + .await + .unwrap(); + } + + // Verify entries are in memory but not durable + assert_eq!(original_ctx.raft_log.entries.len(), 3); + assert_eq!( + original_ctx.raft_log.durable_index.load(Ordering::Acquire), + 0 + ); + + // Recover from the same storage (simulating restart) + let recovered_ctx = original_ctx.recover_from_crash(); + + // Simulate crash by dropping the original context without flushing + drop(original_ctx); + + sleep(Duration::from_millis(50)).await; // Allow recovery + + // Verify recovery - for MemFirst without flush, data should be lost + assert_eq!( + recovered_ctx.raft_log.durable_index.load(Ordering::Acquire), + 0 + ); + assert_eq!(recovered_ctx.raft_log.entries.len(), 0); + + // No entries should be available + for i in 1..=3 { + let entry = recovered_ctx.raft_log.entry(i).unwrap(); + assert!(entry.is_none()); + } + } + + #[tokio::test] + async fn test_high_concurrency() { + let ctx = TestContext::new( + PersistenceStrategy::DiskFirst, + FlushPolicy::Immediate, + "test_high_concurrency", + ); + let mut handles = vec![]; + + for i in 0..10 { + let log = ctx.raft_log.clone(); + handles.push(tokio::spawn(async move { + for j in 1..=100 { + let index = i * 100 + j; + log.append_entries(vec![Entry { + index, + term: 1, + payload: None, + }]) + .await + .unwrap(); + } + })); + } + + for handle in handles { + handle.await.unwrap(); + } + + // Verify all entries persisted + assert_eq!(ctx.raft_log.durable_index.load(Ordering::Acquire), 1000); + assert_eq!(ctx.raft_log.entries.len(), 1000); + } +} + +mod mem_first_tests { + + use super::*; + use crate::LogStore; + use crate::StorageEngine; + + #[tokio::test] + async fn test_basic_write_before_persist() { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_basic_write_before_persist", + ); + ctx.append_entries(1, 5, 1).await; + + // Verify in memory but not yet durable + assert_eq!(ctx.raft_log.entries.len(), 5); + assert_eq!(ctx.raft_log.durable_index.load(Ordering::Acquire), 0); + } + + #[tokio::test] + async fn test_async_persistence() { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_async_persistence", + ); + ctx.append_entries(1, 100, 1).await; + + // Trigger flush + ctx.raft_log.flush().await.unwrap(); + + // Verify persistence + assert_eq!(ctx.raft_log.durable_index.load(Ordering::Acquire), 100); + assert_eq!(ctx.storage.as_ref().log_store().last_index(), 100); + } + + #[tokio::test] + async fn test_power_loss_data_loss() { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_power_loss_data_loss", + ); + ctx.append_entries(1, 100, 1).await; + + // Simulate power loss before flush + // storage.reset() simulates data loss after crash + + // In real scenario, durable_index would be 0 after restart + assert_eq!(ctx.raft_log.durable_index.load(Ordering::Acquire), 0); + } + + #[tokio::test] + async fn test_high_concurrency_memory_only() { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_high_concurrency_memory_only", + ); + let mut handles = vec![]; + + for i in 0..10 { + let log = ctx.raft_log.clone(); + handles.push(tokio::spawn(async move { + for j in 1..=100 { + let index = i * 100 + j; + log.append_entries(vec![Entry { + index, + term: 1, + payload: None, + }]) + .await + .unwrap(); + } + })); + } + + for handle in handles { + handle.await.unwrap(); + } + + // Verify all entries in memory + assert_eq!(ctx.raft_log.entries.len(), 1000); + assert_eq!(ctx.raft_log.last_entry_id(), 1000); + } +} + +mod batched_tests { + use super::*; + + #[tokio::test] + async fn test_batch_flush_trigger() { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 10, + interval_ms: 100, + }, + "test_batch_flush_trigger", + ); + + // Add 9 entries - should not trigger flush + ctx.append_entries(1, 9, 1).await; + assert_eq!(ctx.raft_log.durable_index.load(Ordering::Acquire), 0); + + // Add 1 more to reach batch size + ctx.append_entries(10, 1, 1).await; + sleep(Duration::from_millis(50)).await; // Allow time for async processing + + // Should have flushed first 10 + assert_eq!(ctx.raft_log.durable_index.load(Ordering::Acquire), 10); + } + + #[tokio::test] + async fn test_timed_flush_trigger() { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 100, + interval_ms: 50, + }, + "test_timed_flush_trigger", + ); + ctx.append_entries(1, 50, 1).await; + + // Wait for timer to trigger + sleep(Duration::from_millis(75)).await; + + // Should have flushed via timer + assert!(ctx.raft_log.durable_index.load(Ordering::Acquire) > 0); + } + + #[tokio::test] + async fn test_partial_flush_after_crash() { + let batch_size = 50; + + // Create and partially populate storage + { + // let (db, _) = reset_dbs(db_path.to_str().unwrap()); + + // let log_tree = db.open_tree("raft_log_tree").unwrap(); + // let meta_tree = db.open_tree("raft_meta_tree").unwrap(); + // let storage = Arc::new(FileStorageEngine::new()); + let (raft_log, receiver) = + BufferedRaftLog::>::new( + 1, + PersistenceConfig { + strategy: PersistenceStrategy::MemFirst, + flush_policy: FlushPolicy::Batch { + threshold: batch_size, + interval_ms: 100, + }, + max_buffered_entries: 1000, + ..Default::default() + }, + give_me_mock_storage(), + ); + let raft_log = raft_log.start(receiver); + + // Add 75 entries (1.5 batches) + for i in 1..=75 { + raft_log + .append_entries(vec![Entry { + index: i, + term: 1, + payload: None, + }]) + .await + .unwrap(); + } + + // Simulate crash without proper shutdown + } + + // Recover from disk + // let (db, _) = reuse_dbs(db_path.to_str().unwrap()); + + // let log_tree = db.open_tree("raft_log_tree").unwrap(); + // let meta_tree = db.open_tree("raft_meta_tree").unwrap(); + // let storage = Arc::new(FileStorageEngine::new()); + let (raft_log, receiver) = + BufferedRaftLog::>::new( + 1, + PersistenceConfig { + strategy: PersistenceStrategy::MemFirst, + flush_policy: FlushPolicy::Batch { + threshold: batch_size, + interval_ms: 100, + }, + max_buffered_entries: 1000, + ..Default::default() + }, + give_me_mock_storage(), + ); + let raft_log = raft_log.start(receiver); + sleep(Duration::from_millis(50)).await; // Allow recovery + + // Only first batch should be durable + assert_eq!(raft_log.durable_index.load(Ordering::Acquire), 0); //because batch threshold is 100 + assert!(raft_log.entry(60).unwrap().is_none()); // Not persisted + } +} + +// Generic tests for all strategies +mod common_tests { + use super::*; + + #[tokio::test] + async fn test_log_compaction() { + let ctx = TestContext::new( + PersistenceStrategy::DiskFirst, + FlushPolicy::Immediate, + "test_log_compaction", + ); + ctx.append_entries(1, 100, 1).await; + + // Compact first 50 entries + ctx.raft_log.purge_logs_up_to(LogId { index: 50, term: 1 }).await.unwrap(); + + // Verify compaction + assert!(ctx.raft_log.entry(25).unwrap().is_none()); + assert_eq!(ctx.raft_log.first_entry_id(), 51); + assert_eq!(ctx.raft_log.durable_index.load(Ordering::Acquire), 100); + } + + #[tokio::test] + async fn test_term_index_calculation() { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_term_index_calculation", + ); + + // Add entries with different terms + ctx.append_entries(1, 50, 1).await; + ctx.append_entries(51, 50, 2).await; + ctx.append_entries(101, 50, 3).await; + + // Verify term indices + assert_eq!(ctx.raft_log.first_index_for_term(2), Some(51)); + assert_eq!(ctx.raft_log.last_index_for_term(2), Some(100)); + assert_eq!(ctx.raft_log.last_index_for_term(3), Some(150)); + } + + #[tokio::test] + async fn test_drop_behavior() { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 10, + interval_ms: 100, + }, + "test_drop_behavior", + ); + ctx.append_entries(1, 5, 1).await; + + // Explicitly drop context + drop(ctx); + + // Test passes if no deadlock occurs during drop + } + + #[tokio::test] + async fn test_reset_operation() { + let ctx = TestContext::new( + PersistenceStrategy::DiskFirst, + FlushPolicy::Immediate, + "test_reset_operation", + ); + ctx.append_entries(1, 100, 1).await; + + // Reset log + ctx.raft_log.reset().await.unwrap(); + + // Verify reset state + assert!(ctx.raft_log.is_empty()); + assert_eq!(ctx.raft_log.durable_index.load(Ordering::Acquire), 0); + assert_eq!(ctx.raft_log.next_id.load(Ordering::Acquire), 1); + } +} + +mod filter_out_conflicts_and_append_performance_tests { + use super::*; + use crate::FileStorageEngine; + + #[tokio::test] + async fn test_filter_out_conflicts_performance_consistent_across_flush_intervals_fresh_cluster() + { + // Test configuration + let test_cases = vec![ + (10, 50), // 10ms interval, 50ms max duration + (100, 50), // 100ms interval, 50ms max duration + (1000, 50), // 1000ms interval, 50ms max duration + ]; + + for (interval_ms, max_duration_ms) in test_cases { + // Create MemFirst storage with batch policy + let config = PersistenceConfig { + strategy: PersistenceStrategy::MemFirst, + flush_policy: FlushPolicy::Batch { + threshold: 1000, + interval_ms, + }, + max_buffered_entries: 1000, + ..Default::default() + }; + + // let mut log_store = MockLogStore::new(); + // log_store.expect_last_index().returning(|| 0); + // log_store.expect_truncate().returning(|_| Ok(())); + // log_store.expect_persist_entries().returning(|_| Ok(())); + // log_store.expect_reset().returning(|| Ok(())); + + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path().to_path_buf(); + + let (log, receiver) = BufferedRaftLog::< + RaftTypeConfig, + >::new( + 1, config, Arc::new(FileStorageEngine::new(path).unwrap()) + ); + let log = log.start(receiver); + + // Populate with test data (1000 entries) + let mut entries = vec![]; + for i in 1..=1000 { + entries.push(Entry { + index: i, + term: 1, + payload: Some(EntryPayload::command(Bytes::from(vec![0; 256]))), // 256B payload + }); + } + log.append_entries(entries.clone()).await.unwrap(); + + // Measure conflict resolution performance + let start = Instant::now(); + log.filter_out_conflicts_and_append( + 0, // prev_log_index + 0, // prev_log_term + vec![Entry { + index: 501, + term: 1, + payload: Some(EntryPayload::command(Bytes::from(vec![1; 256]))), + }], + ) + .await + .unwrap(); + + let duration = start.elapsed().as_millis() as u64; + println!("Interval {interval_ms}ms: Took {duration}ms"); + + // Verify performance consistency + assert!( + duration <= max_duration_ms, + "Duration {duration}ms exceeds max {max_duration_ms}ms for {interval_ms}ms interval" + ); + + // Verify correctness + assert!(log.entry(500).unwrap().is_none()); + assert!(log.entry(501).unwrap().is_some()); + assert!(log.entry(502).unwrap().is_none()); + } + } + + #[tokio::test] + async fn test_filter_out_conflicts_performance_consistent_across_flush_intervals() { + // Test configuration + let test_cases = vec![ + (10, 50), // 10ms interval, 50ms max duration + (100, 50), // 100ms interval, 50ms max duration + (1000, 50), // 1000ms interval, 50ms max duration + ]; + + for (interval_ms, max_duration_ms) in test_cases { + // Create MemFirst storage with batch policy + let config = PersistenceConfig { + strategy: PersistenceStrategy::MemFirst, + flush_policy: FlushPolicy::Batch { + threshold: 1000, + interval_ms, + }, + max_buffered_entries: 1000, + ..Default::default() + }; + + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path().to_path_buf(); + + let (log, receiver) = BufferedRaftLog::< + RaftTypeConfig, + >::new( + 1, config, Arc::new(FileStorageEngine::new(path).unwrap()) + ); + let log = log.start(receiver); + + // Populate with test data (1000 entries) + let mut entries = vec![]; + for i in 1..=1000 { + entries.push(Entry { + index: i, + term: 1, + payload: Some(EntryPayload::command(Bytes::from(vec![0; 256]))), // 256B payload + }); + } + log.append_entries(entries.clone()).await.unwrap(); + + // Measure conflict resolution performance + let start = Instant::now(); + log.filter_out_conflicts_and_append( + 500, // prev_log_index + 1, // prev_log_term + vec![Entry { + index: 501, + term: 1, + payload: Some(EntryPayload::command(Bytes::from(vec![1; 256]))), + }], + ) + .await + .unwrap(); + + let duration = start.elapsed().as_millis() as u64; + println!("Interval {interval_ms}ms: Took {duration}ms"); + + // Verify performance consistency + assert!( + duration <= max_duration_ms, + "Duration {duration}ms exceeds max {max_duration_ms}ms for {interval_ms}ms interval" + ); + + // Verify correctness + assert!(log.entry(500).unwrap().is_some()); + assert!(log.entry(501).unwrap().is_some()); + assert!(log.entry(502).unwrap().is_none()); // Conflict removed + } + } +} + +mod performance_tests { + use std::sync::Arc; + + use tokio::sync::Barrier; + use tokio::time::Duration; + + use super::*; + use crate::MockLogStore; + use crate::MockMetaStore; + use crate::test_utils::MockStorageEngine; + + // Test helper: Creates storage with controllable delay + fn create_delayed_storage(delay_ms: u64) -> Arc { + let mut log_store = MockLogStore::new(); + log_store.expect_last_index().returning(|| 0); + log_store.expect_truncate().returning(|_| Ok(())); + log_store.expect_reset().returning(|| Ok(())); + log_store.expect_flush().returning(|| Ok(())); + + // Add controllable delay to persist_entries + log_store.expect_persist_entries().returning(move |_| { + let delay = Duration::from_millis(delay_ms); + std::thread::sleep(delay); + Ok(()) + }); + + Arc::new(MockStorageEngine::from(log_store, MockMetaStore::new())) + } + + // 1. Tests reset performance during active flush + #[tokio::test] + async fn test_reset_performance_during_active_flush() { + const FLUSH_DELAY_MS: u64 = 500; + let is_ci = std::env::var("CI").is_ok(); + let max_reset_duration_ms = if is_ci { 500 } else { 50 }; + + let test_cases = vec![ + ( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1000, + interval_ms: 1000, + }, + ), + (PersistenceStrategy::DiskFirst, FlushPolicy::Immediate), + ]; + + for (strategy, flush_policy) in test_cases { + let storage = create_delayed_storage(FLUSH_DELAY_MS); + let config = PersistenceConfig { + strategy: strategy.clone(), + flush_policy: flush_policy.clone(), + max_buffered_entries: 1000, + ..Default::default() + }; + + let (log, receiver) = BufferedRaftLog::::new(1, config, storage); + let log_arc = Arc::new(log.start(receiver)); + let barrier = Arc::new(Barrier::new(2)); + + // Start long-running flush in background + let flush_log = log_arc.clone(); + let flush_barrier = barrier.clone(); + tokio::spawn(async move { + let indexes: Vec = (1..=1000).collect(); + flush_barrier.wait().await; // Sync point + let _ = flush_log.process_flush(&indexes).await; + }); + + // Wait for flush to start + barrier.wait().await; + + // Measure reset performance during active flush + let start = Instant::now(); + log_arc.reset().await.unwrap(); + let duration = start.elapsed(); + + assert!( + duration.as_millis() < max_reset_duration_ms as u128, + "Reset took {}ms during active flush ({:?}/{:?})", + duration.as_millis(), + strategy, + flush_policy + ); + } + } + + // 2. Tests filter_out_conflicts performance with active flush + #[tokio::test] + async fn test_filter_conflicts_performance_during_flush() { + let is_ci = std::env::var("CI").is_ok(); + // Relax time limit in CI environment + let test_cases = if is_ci { + vec![(10, 500), (100, 500), (1000, 500)] + } else { + vec![(10, 50), (100, 50), (1000, 50)] + }; + + const FLUSH_DELAY_MS: u64 = 300; + // const MAX_DURATION_MS: u64 = 50; + + // let test_cases = vec![(10, 50), (100, 50), (1000, 50)]; + + for (interval_ms, max_duration_ms) in test_cases { + let storage = create_delayed_storage(FLUSH_DELAY_MS); + let config = PersistenceConfig { + strategy: PersistenceStrategy::MemFirst, + flush_policy: FlushPolicy::Batch { + threshold: 1000, + interval_ms, + }, + max_buffered_entries: 1000, + ..Default::default() + }; + + let (log, receiver) = BufferedRaftLog::::new(1, config, storage); + let log_arc = Arc::new(log.start(receiver)); + let barrier = Arc::new(Barrier::new(2)); + + // Populate with test data + let mut entries = vec![]; + for i in 1..=1000 { + entries.push(Entry { + index: i, + term: 1, + payload: Some(EntryPayload::command(Bytes::from(vec![0; 256]))), + }); + } + log_arc.append_entries(entries).await.unwrap(); + + // Start long flush in background + let flush_log = log_arc.clone(); + let flush_barrier = barrier.clone(); + tokio::spawn(async move { + let indexes: Vec = (1..=1000).collect(); + flush_barrier.wait().await; + let _ = flush_log.process_flush(&indexes).await; + }); + + // Wait for flush to start + barrier.wait().await; + + // Measure performance during active flush + let start = Instant::now(); + log_arc + .filter_out_conflicts_and_append( + 500, + 1, + vec![Entry { + index: 501, + term: 1, + payload: Some(EntryPayload::command(Bytes::from(vec![1; 256]))), + }], + ) + .await + .unwrap(); + + let duration = start.elapsed(); + assert!( + duration.as_millis() < max_duration_ms as u128, + "Operation took {}ms with {}ms interval during flush", + duration.as_millis(), + interval_ms + ); + } + } + + // 3. Tests fresh cluster performance consistency + #[tokio::test] + async fn test_fresh_cluster_performance_consistency() { + let is_ci = std::env::var("CI").is_ok(); + // Relax time limit in CI environment + let max_duration_ms = if is_ci { 50 } else { 5 }; + + let test_cases = vec![ + ( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1000, + interval_ms: 1000, + }, + ), + (PersistenceStrategy::DiskFirst, FlushPolicy::Immediate), + ]; + + for (strategy, flush_policy) in test_cases { + let mut log_store = MockLogStore::new(); + log_store.expect_flush().return_once(|| Ok(())); + log_store.expect_last_index().returning(|| 0); + log_store.expect_truncate().returning(|_| Ok(())); + log_store.expect_persist_entries().returning(|_| Ok(())); + log_store.expect_reset().returning(|| Ok(())); + + let config = PersistenceConfig { + strategy: strategy.clone(), + flush_policy: flush_policy.clone(), + max_buffered_entries: 1000, + ..Default::default() + }; + + let (log, receiver) = BufferedRaftLog::::new( + 1, + config, + Arc::new(MockStorageEngine::from(log_store, MockMetaStore::new())), + ); + let log_arc = Arc::new(log.start(receiver)); + + // Measure reset performance in fresh cluster + let start = Instant::now(); + log_arc.reset().await.unwrap(); + let duration = start.elapsed(); + + assert!( + duration.as_millis() < max_duration_ms as u128, + "Fresh cluster reset took {}ms ({:?}/{:?})", + duration.as_millis(), + strategy, + flush_policy + ); + } + } + + // // 4. Tests command processing during long flush + // #[tokio::test] + // async fn test_command_processing_during_flush() { + // ; + // const FLUSH_DELAY_MS: u64 = 800; + // const MAX_COMMAND_DURATION_MS: u64 = 10; + + // let storage = create_delayed_storage(FLUSH_DELAY_MS); + // let config = PersistenceConfig { + // strategy: PersistenceStrategy::MemFirst, + // flush_policy: FlushPolicy::Batch { + // threshold: 1, + // interval_ms: 1000, + // }, + // max_buffered_entries: 1000, + // }; + + // let (log, mut receiver) = BufferedRaftLog::::new(1, config, storage); + // let log_arc = Arc::new(log.start(receiver)); + // let barrier = Arc::new(Barrier::new(2)); + + // // Start long-running flush + // let flush_log = log_arc.clone(); + // let flush_barrier = barrier.clone(); + // tokio::spawn(async move { + // let indexes = vec![1]; + // flush_barrier.wait().await; + // let _ = flush_log.process_flush(&indexes).await; + // }); + + // // Wait for flush to start + // barrier.wait().await; + + // // Test multiple command types during flush + // let commands = vec![ + // LogCommand::Flush(oneshot::channel().0), + // LogCommand::Reset(oneshot::channel().0), + // LogCommand::PersistEntries(vec![2]), + // LogCommand::WaitDurable(2, oneshot::channel().0), + // ]; + + // for cmd in commands { + // let start = Instant::now(); + // log_arc.command_sender.send(cmd).unwrap(); + + // // Verify command is processed quickly + // match timeout(Duration::from_millis(MAX_COMMAND_DURATION_MS), receiver.recv()).await + // { Ok(Some(_)) => { + // let duration = start.elapsed(); + // assert!( + // duration.as_millis() < MAX_COMMAND_DURATION_MS as u128, + // "Command processing took {}ms during flush", + // duration.as_millis() + // ); + // } + // _ => panic!("Command not processed within {}ms", MAX_COMMAND_DURATION_MS), + // } + // } + // } +} + +mod batch_processor_tests { + use std::sync::Arc; + use std::sync::atomic::AtomicBool; + use std::sync::atomic::Ordering; + use std::time::Duration; + + use tokio::sync::Notify; + use tokio::sync::mpsc; + use tokio::sync::oneshot; + + use super::*; + + struct TestLog { + // Used to observe if reset was called + reset_called: Arc, + // Used to simulate slow flush + flush_notify: Arc, + // Used to observe flush trigger + flush_triggered: Arc, + } + + impl TestLog { + fn new() -> Self { + Self { + reset_called: Arc::new(AtomicBool::new(false)), + flush_notify: Arc::new(Notify::new()), + flush_triggered: Arc::new(AtomicBool::new(false)), + } + } + } + + #[tokio::test] + async fn batch_processor_handles_command_while_flushing() { + // Setup + let test_log = TestLog::new(); + + // Simulate the actual struct with only what's needed for the test + struct DummyLog { + test_log: TestLog, + } + impl DummyLog { + async fn handle_command( + &self, + cmd: LogCommand, + ) { + if let LogCommand::Reset(_ack) = cmd { + self.test_log.reset_called.store(true, Ordering::SeqCst); + } + } + async fn process_flush( + &self, + _indexes: &[u64], + ) -> Result<(), ()> { + self.test_log.flush_triggered.store(true, Ordering::SeqCst); + // Simulate slow flush + self.test_log.flush_notify.notified().await; + Ok(()) + } + async fn get_pending_indexes(&self) -> Vec { + vec![1, 2, 3] + } + } + + // Arc/Weak for test + let dummy_log = Arc::new(DummyLog { test_log }); + let weak_log = Arc::downgrade(&dummy_log); + + // Command channel + let (tx, mut rx) = mpsc::unbounded_channel(); + + // Spawn batch_processor (copied and adapted for test) + let flush_notify = dummy_log.test_log.flush_notify.clone(); + let flush_triggered = dummy_log.test_log.flush_triggered.clone(); + let reset_called = dummy_log.test_log.reset_called.clone(); + let processor = tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_millis(50)); + loop { + tokio::select! { + cmd = rx.recv() => { + if let Some(cmd) = cmd { + if let Some(this) = weak_log.upgrade() { + this.handle_command(cmd).await; + } + } else { + break; + } + } + _ = interval.tick() => { + if let Some(this) = weak_log.upgrade() { + let indexes = this.get_pending_indexes().await; + if !indexes.is_empty() { + let this_clone = Arc::clone(&this); + tokio::spawn(async move { + let _ = this_clone.process_flush(&indexes).await; + }); + } + } + } + } + } + }); + + // Send a command to trigger flush + tx.send(LogCommand::PersistEntries(vec![])).unwrap(); + + // Wait for flush to be triggered + tokio::time::timeout(Duration::from_millis(200), async { + while !flush_triggered.load(Ordering::SeqCst) { + tokio::time::sleep(Duration::from_millis(10)).await; + } + }) + .await + .expect("Flush should be triggered"); + + // While flush is ongoing (not released), send Reset + let (one_tx, _one_rx) = oneshot::channel(); + tx.send(LogCommand::Reset(one_tx)).unwrap(); + + // Wait a bit to let command process + tokio::time::sleep(Duration::from_millis(20)).await; + + // Check that Reset was handled even while flush is not finished + assert!( + reset_called.load(Ordering::SeqCst), + "Reset command should be handled promptly" + ); + + // Release flush + flush_notify.notify_waiters(); + + // Cleanup: shutdown processor + drop(tx); + processor.await.unwrap(); + } +} + +mod save_load_hard_state_tests { + use super::*; + use crate::FileStorageEngine; + use crate::HardState; + use crate::LogStore; + use crate::MetaStore; + use crate::StorageEngine; + use d_engine_proto::server::election::VotedFor; + + /// Test that hard state operations use the meta tree and not the log tree + #[tokio::test] + async fn test_hard_state_uses_meta_tree_not_log_tree() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_hard_state_uses_meta_tree_not_log_tree", + ); + + // Create test hard state + let test_hard_state = HardState { + current_term: 5, + voted_for: Some(VotedFor { + voted_for_id: 10, + voted_for_term: 7, + }), + }; + + // Save through buffered raft log + context + .raft_log + .save_hard_state(&test_hard_state) + .expect("save_hard_state should succeed"); + + // Verify storage engine state + // 1. Meta tree should contain the hard state + let decoded_meta = context + .storage + .meta_store() + .load_hard_state() + .expect("meta_tree.get should succeed") + .expect("hard state should exist in meta tree"); + + // let decoded_meta: HardState = bincode::deserialize(&meta_value).unwrap(); + assert_eq!(decoded_meta.current_term, 5); + assert_eq!( + decoded_meta.voted_for, + Some(VotedFor { + voted_for_id: 10, + voted_for_term: 7, + }) + ); + + // // 2. Log tree should NOT contain the hard state key + // let log_value = context + // .storage + // .log_store() + // .get(HARD_STATE_KEY) + // .expect("log_tree.get should succeed"); + // assert!( + // log_value.is_none(), + // "Hard state key should not exist in log tree" + // ); + + // 3. Load through buffered raft log + let loaded = context + .raft_log + .load_hard_state() + .expect("load_hard_state should succeed") + .expect("hard state should exist"); + assert_eq!(loaded.current_term, 5); + assert_eq!( + loaded.voted_for, + Some(VotedFor { + voted_for_id: 10, + voted_for_term: 7, + }) + ); + } + + /// Test that hard state survives restarts and uses the correct tree + #[tokio::test] + async fn test_hard_state_persistence_across_restart() { + let node_id = 1; + + let tempdir = tempfile::tempdir().unwrap(); + let file_path = tempdir.path().to_path_buf(); + // Phase 1: Initial save + { + let storage = Arc::new(FileStorageEngine::new(file_path.clone()).unwrap()); + let (raft_log, receiver) = + BufferedRaftLog::>::new( + node_id, + PersistenceConfig { + strategy: PersistenceStrategy::MemFirst, + flush_policy: FlushPolicy::Immediate, + max_buffered_entries: 1000, + ..Default::default() + }, + storage, + ); + let raft_log = raft_log.start(receiver); + + let hard_state = HardState { + current_term: 8, + voted_for: Some(VotedFor { + voted_for_id: 3, + voted_for_term: 8, + }), + }; + raft_log.save_hard_state(&hard_state).expect("save should succeed"); + + // Explicitly flush to ensure data is persisted + raft_log.log_store.flush().expect("flush should succeed"); + raft_log.meta_store.flush().expect("flush should succeed"); + } + + // Phase 2: Restart + { + let storage = Arc::new(FileStorageEngine::new(file_path).unwrap()); + let (raft_log, receiver) = + BufferedRaftLog::>::new( + node_id, + PersistenceConfig { + strategy: PersistenceStrategy::MemFirst, + flush_policy: FlushPolicy::Immediate, + max_buffered_entries: 1000, + ..Default::default() + }, + storage, + ); + let raft_log = raft_log.start(receiver); + + // Verify hard state loaded correctly + let loaded = raft_log + .load_hard_state() + .expect("load should succeed") + .expect("state should exist"); + assert_eq!(loaded.current_term, 8); + assert_eq!( + loaded.voted_for, + Some(VotedFor { + voted_for_id: 3, + voted_for_term: 8, + }) + ); + + // Verify storage trees + // 1. Meta tree should contain the value + // let meta_value = raft_log + // .meta_store + // .get(HARD_STATE_KEY) + // .expect("meta get should succeed") + // .expect("value should exist"); + // let decoded: HardState = bincode::deserialize(&meta_value).unwrap(); + // assert_eq!(decoded.current_term, 8); + + // // 2. Log tree should NOT contain the value + // let log_value = + // raft_log.meta_store.get(HARD_STATE_KEY).expect("log get should succeed"); + // assert!(log_value.is_none(), "Hard state should not be in log tree"); + } + } + + /// Test that reset operation doesn't clear hard state (only log tree) + #[tokio::test] + async fn test_reset_preserves_hard_state() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_reset_preserves_hard_state", + ); + + // Save initial hard state + let hs = HardState { + current_term: 3, + voted_for: Some(VotedFor { + voted_for_id: 5, + voted_for_term: 7, + }), + }; + context.raft_log.save_hard_state(&hs).expect("save should succeed"); + + // Perform reset (should only clear logs) + context.raft_log.reset().await.expect("reset should succeed"); + + // Verify hard state still exists + let loaded = context.raft_log.load_hard_state().expect("load should succeed").unwrap(); + assert_eq!(loaded.current_term, 3); + assert_eq!( + loaded.voted_for, + Some(VotedFor { + voted_for_id: 5, + voted_for_term: 7, + }) + ); + + // Verify meta tree still has the data + // let meta_value = context + // .storage + // .meta_store() + // .get(HARD_STATE_KEY) + // .expect("meta get should succeed") + // .expect("value should exist"); + // assert!(!meta_value.is_empty()); + } +} + +#[tokio::test] +#[traced_test] +async fn test_last_entry_id_performance() { + // Set up test context + let test_context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1_000_000, + interval_ms: 360_000, + }, + "test_last_entry_id_performance", + ); + + // Create a large number of entries + const ENTRY_COUNT: usize = 1_000_000; + let entries: Vec<_> = (0..ENTRY_COUNT) + .map(|index| Entry { + index: index as u64, + term: index as u64, + payload: Some(EntryPayload::command(Bytes::from(vec![1; 256]))), + }) + .collect(); + + // Insert entries into the log + let insert_start = Instant::now(); + test_context.raft_log.append_entries(entries).await.unwrap(); + let insert_duration = insert_start.elapsed(); + println!("Insert duration: {insert_duration:?}"); + + // Measure last_entry_id performance + let mut durations = Vec::with_capacity(10); + let mut last_id = 0; + for _ in 1..1_000 { + let start = Instant::now(); + last_id = test_context.raft_log.last_entry_id(); + let duration = start.elapsed(); + durations.push(duration); + } + + // Calculate average duration + let avg_duration = durations.iter().sum::() / durations.len() as u32; + println!("Average last_entry_id duration: {avg_duration:?}"); + assert!(avg_duration < Duration::from_millis(1)); + // Assert that the last entry ID is correct + assert_eq!(last_id, ENTRY_COUNT as u64 - 1); +} + +#[cfg(test)] +mod remove_range_tests { + use super::*; + use crate::test_utils; + + #[tokio::test] + async fn test_remove_middle_range() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_remove_middle_range", + ); + context.raft_log.reset().await.expect("reset successfully!"); + + // Insert 100 entries + test_utils::simulate_insert_command(&context.raft_log, (1..=100).collect(), 1).await; + assert_eq!(context.raft_log.len(), 100); + assert_eq!(context.raft_log.first_entry_id(), 1); + assert_eq!(context.raft_log.last_entry_id(), 100); + + // Remove middle range + context.raft_log.remove_range(40..=60); + + // Verify removal + assert_eq!(context.raft_log.len(), 79); + assert_eq!(context.raft_log.first_entry_id(), 1); // Min unchanged + assert_eq!(context.raft_log.last_entry_id(), 100); // Max unchanged + + // Verify specific entries + assert!(context.raft_log.entry(39).unwrap().is_some()); + assert!(context.raft_log.entry(40).unwrap().is_none()); + assert!(context.raft_log.entry(60).unwrap().is_none()); + assert!(context.raft_log.entry(61).unwrap().is_some()); + } + + #[tokio::test] + async fn test_remove_from_start() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_remove_from_start", + ); + context.raft_log.reset().await.expect("reset successfully!"); + + // Insert 100 entries + test_utils::simulate_insert_command(&context.raft_log, (1..=100).collect(), 1).await; + + // Remove first 50 entries + context.raft_log.remove_range(1..=50); + + // Verify state + assert_eq!(context.raft_log.len(), 50); + assert_eq!(context.raft_log.first_entry_id(), 51); // Min updated + assert_eq!(context.raft_log.last_entry_id(), 100); // Max unchanged + + // Boundary checks + assert!(context.raft_log.entry(50).unwrap().is_none()); + assert!(context.raft_log.entry(51).unwrap().is_some()); + } + + #[tokio::test] + async fn test_remove_to_end() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_remove_to_end", + ); + context.raft_log.reset().await.expect("reset successfully!"); + + // Insert 100 entries + test_utils::simulate_insert_command(&context.raft_log, (1..=100).collect(), 1).await; + + // Remove from 90 to end + context.raft_log.remove_range(90..=u64::MAX); + + // Verify state + assert_eq!(context.raft_log.len(), 89); + assert_eq!(context.raft_log.first_entry_id(), 1); // Min unchanged + assert_eq!(context.raft_log.last_entry_id(), 89); // Max updated + + // Boundary checks + assert!(context.raft_log.entry(89).unwrap().is_some()); + assert!(context.raft_log.entry(90).unwrap().is_none()); + assert!(context.raft_log.entry(100).unwrap().is_none()); + } + + #[tokio::test] + async fn test_remove_empty_range() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_remove_empty_range", + ); + context.raft_log.reset().await.expect("reset successfully!"); + + test_utils::simulate_insert_command(&context.raft_log, vec![1, 2, 3], 1).await; + + // Remove nothing + context.raft_log.remove_range(5..=10); + + assert_eq!(context.raft_log.len(), 3); + assert_eq!(context.raft_log.first_entry_id(), 1); + assert_eq!(context.raft_log.last_entry_id(), 3); + } + + #[tokio::test] + async fn test_remove_entire_log() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_remove_entire_log", + ); + context.raft_log.reset().await.expect("reset successfully!"); + + // Insert 100 entries + test_utils::simulate_insert_command(&context.raft_log, (1..=100).collect(), 1).await; + + // Remove entire log + context.raft_log.remove_range(1..=u64::MAX); + + // Verify state + assert_eq!(context.raft_log.len(), 0); + assert_eq!(context.raft_log.first_entry_id(), 0); + assert_eq!(context.raft_log.last_entry_id(), 0); + assert!(context.raft_log.is_empty()); + } + + #[tokio::test] + async fn test_remove_range_with_concurrent_reads() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_remove_range_with_concurrent_reads", + ); + context.raft_log.reset().await.expect("reset successfully!"); + + // Insert 1000 entries + test_utils::simulate_insert_command(&context.raft_log, (1..=1000).collect(), 1).await; + + // Spawn concurrent readers + let readers: Vec<_> = (0..10) + .map(|_| { + let log = context.raft_log.clone(); + tokio::spawn(async move { + for i in 1..=1000 { + // This shouldn't panic even during removal + let _ = log.entry(i); + } + }) + }) + .collect(); + + // Remove a large range while reads are happening + context.raft_log.remove_range(300..=700); + + // Wait for readers to finish + for handle in readers { + handle.await.expect("reader task failed"); + } + + // Verify final state + assert_eq!(context.raft_log.len(), 599); // 299 before + 300 after + assert!(context.raft_log.entry(299).unwrap().is_some()); + assert!(context.raft_log.entry(300).unwrap().is_none()); + assert!(context.raft_log.entry(700).unwrap().is_none()); + assert!(context.raft_log.entry(701).unwrap().is_some()); + } + + #[tokio::test] + async fn test_remove_range_performance() { + // Skip performance tests during coverage runs due to instrumentation overhead + if std::env::var("CARGO_LLVM_COV").is_ok() || std::env::var("CARGO_TARPAULIN").is_ok() { + eprintln!("Skipping performance test during coverage run"); + return; + } + + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_remove_range_performance", + ); + context.raft_log.reset().await.expect("reset successfully!"); + + // Insert 100,000 entries + let entries: Vec = (1..=100_000).collect(); + test_utils::simulate_insert_command(&context.raft_log, entries, 1).await; + + // Measure removal of 50,000 entries + let start = std::time::Instant::now(); + context.raft_log.remove_range(25_001..=75_000); + let duration = start.elapsed(); + + println!("Removed 50,000 entries in {duration:?}"); + assert!(duration < std::time::Duration::from_millis(100)); + + // Verify state + assert_eq!(context.raft_log.len(), 50_000); + assert_eq!( + context.raft_log.entry(25_000).unwrap().unwrap().index, + 25_000 + ); + assert!(context.raft_log.entry(25_001).unwrap().is_none()); + assert!(context.raft_log.entry(75_000).unwrap().is_none()); + assert_eq!( + context.raft_log.entry(75_001).unwrap().unwrap().index, + 75_001 + ); + } + + #[tokio::test] + async fn test_remove_single_entry() { + let context = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1, + interval_ms: 1, + }, + "test_remove_single_entry", + ); + context.raft_log.reset().await.expect("reset successfully!"); + + test_utils::simulate_insert_command(&context.raft_log, vec![1, 2, 3], 1).await; + + // Remove middle entry + context.raft_log.remove_range(2..=2); + + assert_eq!(context.raft_log.len(), 2); + assert_eq!(context.raft_log.first_entry_id(), 1); + assert_eq!(context.raft_log.last_entry_id(), 3); + assert!(context.raft_log.entry(2).unwrap().is_none()); + } +} + +// Add these tests to cover important edge cases and performance scenarios + +#[tokio::test] +#[traced_test] +async fn test_high_concurrency_mixed_operations() { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1000, + interval_ms: 100, + }, + "test_high_concurrency_mixed_operations", + ); + + let mut handles = vec![]; + let start_time = Instant::now(); + + // Spawn concurrent writers + for i in 0..10 { + let log = ctx.raft_log.clone(); + handles.push(tokio::spawn(async move { + for j in 1..=1000 { + let entry = Entry { + index: (i * 1000) + j, + term: 1, + payload: Some(EntryPayload::command(Bytes::from(vec![0; 1024]))), // 1KB payload + }; + log.append_entries(vec![entry]).await.unwrap(); + } + })); + } + + // Spawn concurrent readers + for _ in 0..5 { + let log = ctx.raft_log.clone(); + handles.push(tokio::spawn(async move { + for _ in 0..500 { + let last_index = log.last_entry_id(); + if last_index > 0 { + let _ = log.get_entries_range(1..=last_index.min(100)); + } + tokio::time::sleep(Duration::from_micros(10)).await; + } + })); + } + + // Spawn concurrent term queries + for _ in 0..3 { + let log = ctx.raft_log.clone(); + handles.push(tokio::spawn(async move { + for _ in 0..300 { + let _ = log.first_index_for_term(1); + let _ = log.last_index_for_term(1); + tokio::time::sleep(Duration::from_micros(15)).await; + } + })); + } + + for handle in handles { + handle.await.unwrap(); + } + + let duration = start_time.elapsed(); + println!("Mixed operations completed in: {duration:?}"); + + // Verify data integrity + assert_eq!(ctx.raft_log.len(), 10000); + assert!( + duration < Duration::from_secs(10), + "Operations took too long: {duration:?}" + ); +} + +#[tokio::test] +#[traced_test] +async fn test_extreme_boundary_conditions() { + let ctx = TestContext::new( + PersistenceStrategy::DiskFirst, + FlushPolicy::Immediate, + "test_extreme_boundary_conditions", + ); + + // Test with maximum index values + let max_index = u64::MAX - 10; + let entries = vec![ + Entry { + index: max_index, + term: 1, + payload: Some(EntryPayload::command(Bytes::from(b"max_data".to_vec()))), + }, + Entry { + index: max_index + 1, + term: 1, + payload: Some(EntryPayload::command(Bytes::from(b"max_data+1".to_vec()))), + }, + ]; + + ctx.raft_log.append_entries(entries).await.unwrap(); + + // Verify extreme values are handled correctly + assert_eq!(ctx.raft_log.last_entry_id(), max_index + 1); + assert!(ctx.raft_log.entry(max_index).unwrap().is_some()); + assert!(ctx.raft_log.entry(max_index + 1).unwrap().is_some()); +} + +#[tokio::test] +#[traced_test] +async fn test_recovery_under_different_scenarios() { + // Test various recovery scenarios + let scenarios = vec![ + // MemFirst with Immediate flush - should persist everything + (PersistenceStrategy::MemFirst, FlushPolicy::Immediate, 100), + // MemFirst with batch flushing - only persists when threshold met + ( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 50, + interval_ms: 10, + }, + 100, + ), + ( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 200, + interval_ms: 10, + }, + 0, + ), + // DiskFirst should always persist + (PersistenceStrategy::DiskFirst, FlushPolicy::Immediate, 100), + ]; + + for (strategy, flush_policy, expected_recovery) in scenarios { + let instance_id = format!("recovery_test_{strategy:?}_{flush_policy:?}"); + let original_ctx = TestContext::new(strategy.clone(), flush_policy.clone(), &instance_id); + + // Add test data + for i in 1..=100 { + original_ctx + .raft_log + .append_entries(vec![Entry { + index: i, + term: 1, + payload: Some(EntryPayload::command(Bytes::from( + format!("data{i}").into_bytes(), + ))), + }]) + .await + .unwrap(); + } + + // For DiskFirst and Immediate flush, explicitly flush to ensure persistence + if matches!(strategy, PersistenceStrategy::DiskFirst) + || matches!(flush_policy, FlushPolicy::Immediate) + { + original_ctx.raft_log.flush().await.unwrap(); + } else if let FlushPolicy::Batch { threshold, .. } = flush_policy { + // For batch policy, only flush if we reached the threshold + if 100 >= threshold { + original_ctx.raft_log.flush().await.unwrap(); + tokio::time::sleep(Duration::from_millis(100)).await; // KEY!!! + } + } + + // Simulate crash and recovery + let recovered_ctx = original_ctx.recover_from_crash(); + drop(original_ctx); + + // Verify recovery based on expected behavior + assert_eq!( + recovered_ctx.raft_log.len(), + expected_recovery, + "Recovery mismatch for strategy {strategy:?} policy {flush_policy:?}" + ); + } +} + +#[tokio::test] +#[traced_test] +async fn test_performance_benchmarks() { + // Skip performance tests during coverage runs due to instrumentation overhead + if std::env::var("CARGO_LLVM_COV").is_ok() || std::env::var("CARGO_TARPAULIN").is_ok() { + eprintln!("Skipping performance test during coverage run"); + return; + } + + let is_ci = std::env::var("CI").is_ok(); + + // Adjust test parameters according to the environment + let operations = if is_ci { + // CI environment uses a more relaxed threshold + [ + ("append_entries", 500, 500.0), + ("get_entries_range", 2500, 25000.0), + ("entry_lookup", 5000, 100000.0), + ("term_queries", 4000, 25000.0), + ] + } else { + // Local environment uses a stricter threshold + [ + ("append_entries", 1000, 1000.0), + ("get_entries_range", 5000, 50000.0), + ("entry_lookup", 10000, 200000.0), + ("term_queries", 8000, 50000.0), + ] + }; + + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1000, + interval_ms: 100, + }, + "performance_benchmark", + ); + + // Pre-populate with data + let mut entries = Vec::new(); + // Reduce pre-population data in a CI environment + let pre_populate_count = if is_ci { 5000 } else { 10000 }; + + for i in 1..=pre_populate_count { + entries.push(Entry { + index: i, + term: i / 100 + 1, // Vary terms + payload: Some(EntryPayload::command(Bytes::from(vec![0; 256]))), // 256B payload + }); + } + ctx.raft_log.append_entries(entries).await.unwrap(); + + let mut results = HashMap::new(); + + for (op_name, count, min_ops_per_sec) in operations { + let start = Instant::now(); + + match op_name { + "append_entries" => { + for i in 0..count { + let entry = Entry { + index: 10000 + i as u64 + 1, + term: 101, + payload: Some(EntryPayload::command(Bytes::from(vec![0; 256]))), + }; + ctx.raft_log.append_entries(vec![entry]).await.unwrap(); + } + } + "get_entries_range" => { + for i in 0..count { + let start_idx = (i % 9000) as u64 + 1; + let end_idx = start_idx + 100; + let _ = ctx.raft_log.get_entries_range(start_idx..=end_idx); + } + } + "entry_lookup" => { + for i in 0..count { + let index = (i % 10000) as u64 + 1; + let _ = ctx.raft_log.entry(index); + } + } + "term_queries" => { + for i in 0..count { + let term = (i % 100) as u64 + 1; + let _ = ctx.raft_log.first_index_for_term(term); + let _ = ctx.raft_log.last_index_for_term(term); + } + } + _ => {} + } + + let duration = start.elapsed(); + let ops_per_sec = count as f64 / duration.as_secs_f64(); + results.insert(op_name, (duration, ops_per_sec)); + + println!("{op_name}: {count} operations in {duration:?} ({ops_per_sec:.2} ops/sec)"); + + // Performance assertions with more realistic thresholds + assert!( + ops_per_sec > min_ops_per_sec, + "{op_name} operations too slow: {ops_per_sec:.2} ops/sec (expected > {min_ops_per_sec:.2})" + ); + } +} + +#[tokio::test] +async fn test_worker_retry_survives_transient_errors() { + // Tests fix for Issue B: Worker exits prematurely after retry + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 10, + interval_ms: 50, + }, + "test_worker_retry_survives", + ); + + // Append entries that will trigger flush + for i in 1..=100 { + ctx.append_entries(i, 1, 1).await; + } + + // Wait for flush workers to process + tokio::time::sleep(Duration::from_millis(200)).await; + + // Verify all workers are still alive by checking continued processing + ctx.append_entries(101, 50, 1).await; + tokio::time::sleep(Duration::from_millis(100)).await; + + assert_eq!(ctx.raft_log.last_entry_id(), 150); +} + +#[tokio::test] +async fn test_durable_index_monotonic_under_concurrency() { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Immediate, + "test_durable_index_monotonic", + ); + + let mut handles = vec![]; + + for batch in 0..10 { + let log = ctx.raft_log.clone(); + handles.push(tokio::spawn(async move { + let start = batch * 100 + 1; + let entries: Vec = (start..start + 100) + .map(|i| Entry { + index: i, + term: 1, + payload: None, + }) + .collect(); + log.append_entries(entries).await.unwrap(); + })); + } + + join_all(handles).await; + + // Wait for flush to complete + tokio::time::sleep(Duration::from_millis(200)).await; + + // Verify monotonicity + let durable = ctx.raft_log.durable_index.load(Ordering::Acquire); + assert!(durable >= 1000, "durable_index should reach max value"); + + // Verify no entries lost + assert_eq!(ctx.raft_log.len(), 1000); +} + +#[tokio::test] +async fn test_shutdown_closes_channel_properly() { + // Better approach: Test shutdown through Drop behavior + let node_id = 1; + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path().to_path_buf(); + + let _worker_handles = { + let storage = Arc::new(FileStorageEngine::new(path.clone()).unwrap()); + let (raft_log, receiver) = + BufferedRaftLog::>::new( + node_id, + PersistenceConfig { + strategy: PersistenceStrategy::MemFirst, + flush_policy: FlushPolicy::Batch { + threshold: 10, + interval_ms: 100, + }, + max_buffered_entries: 1000, + ..Default::default() + }, + storage, + ); + let raft_log = raft_log.start(receiver); + + // Add some entries + for i in 1..=10 { + raft_log + .append_entries(vec![Entry { + index: i, + term: 1, + payload: None, + }]) + .await + .unwrap(); + } + + // Extract worker handles before drop + let handles: Vec<_> = + raft_log.flush_workers.worker_handles.iter().map(|h| h.is_finished()).collect(); + + // Trigger shutdown via Drop + drop(raft_log); + + handles + }; + + // Wait for shutdown to complete + tokio::time::sleep(Duration::from_millis(200)).await; +} + +// ========== RAFT CORRECTNESS TESTS ========== + +#[tokio::test] +async fn test_memfirst_crash_recovery_durability() { + let instance_id = "test_memfirst_durability"; + + let recovered_path = { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1000, // High threshold to prevent auto-flush + interval_ms: 10000, + }, + instance_id, + ); + + ctx.append_entries(1, 100, 1).await; + + // Verify visible in memory + assert_eq!(ctx.raft_log.len(), 100); + + let path = ctx.path.clone(); + // Simulate crash WITHOUT flush + drop(ctx); + path + }; + + tokio::time::sleep(Duration::from_millis(50)).await; + + // Recovery + let storage = Arc::new(FileStorageEngine::new(PathBuf::from(&recovered_path)).unwrap()); + let (raft_log, receiver) = + BufferedRaftLog::>::new( + 1, + PersistenceConfig { + strategy: PersistenceStrategy::MemFirst, + flush_policy: FlushPolicy::Immediate, + max_buffered_entries: 1000, + ..Default::default() + }, + storage, + ); + let raft_log = raft_log.start(receiver); + + tokio::time::sleep(Duration::from_millis(50)).await; + + // After crash without flush, data should be lost + assert_eq!( + raft_log.len(), + 0, + "MemFirst without flush should lose uncommitted data" + ); +} + +#[tokio::test] +async fn test_diskfirst_crash_recovery_durability() { + let temp_dir = tempfile::tempdir().unwrap(); + let instance_id = "test_diskfirst_durability"; + let storage_path = temp_dir.path().join(instance_id); + + let ctx1 = { + let storage = Arc::new(FileStorageEngine::new(storage_path.clone()).unwrap()); + let (raft_log, receiver) = + BufferedRaftLog::>::new( + 1, + PersistenceConfig { + strategy: PersistenceStrategy::DiskFirst, + flush_policy: FlushPolicy::Immediate, + max_buffered_entries: 1000, + ..Default::default() + }, + storage, + ); + let raft_log = raft_log.start(receiver); + + let entries: Vec<_> = (1..1 + 100) + .map(|index| Entry { + index, + term: 1, + payload: Some(EntryPayload::command(Bytes::from(b"data".to_vec()))), + }) + .collect(); + + raft_log.append_entries(entries).await.unwrap(); + raft_log.flush().await.unwrap(); + assert_eq!(raft_log.len(), 100, "All entries should be recovered"); + + raft_log + }; + + drop(ctx1); + tokio::time::sleep(Duration::from_millis(100)).await; + + // Phase 2: Recovery + let storage = Arc::new(FileStorageEngine::new(PathBuf::from(&storage_path)).unwrap()); + let (raft_log, receiver) = + BufferedRaftLog::>::new( + 1, + PersistenceConfig { + strategy: PersistenceStrategy::DiskFirst, + flush_policy: FlushPolicy::Immediate, + max_buffered_entries: 1000, + ..Default::default() + }, + storage, + ); + let raft_log = raft_log.start(receiver); + + tokio::time::sleep(Duration::from_millis(50)).await; + + // Verify recovery + assert_eq!(raft_log.len(), 100, "All entries should be recovered"); + assert_eq!( + raft_log.durable_index.load(Ordering::Acquire), + 100, + "Durable index should be 100" + ); +} + +#[tokio::test] +async fn test_log_matching_property() { + let ctx = TestContext::new( + PersistenceStrategy::DiskFirst, + FlushPolicy::Immediate, + "test_log_matching", + ); + + // Build log: [1,1] [2,1] [3,2] [4,2] [5,3] + ctx.append_entries(1, 2, 1).await; + ctx.append_entries(3, 2, 2).await; + ctx.append_entries(5, 1, 3).await; + + // Verify consistency + assert_eq!(ctx.raft_log.entry_term(3), Some(2)); + assert_eq!(ctx.raft_log.first_index_for_term(2), Some(3)); + assert_eq!(ctx.raft_log.last_index_for_term(2), Some(4)); + + // Conflict resolution: new leader with [1,1] [2,1] [3,2] [4,3] + let result = ctx + .raft_log + .filter_out_conflicts_and_append( + 3, + 2, + vec![Entry { + index: 4, + term: 3, + payload: None, + }], + ) + .await + .unwrap(); + + assert_eq!(result.unwrap().index, 4); + assert_eq!(ctx.raft_log.entry_term(4), Some(3)); + assert!(ctx.raft_log.entry(5).unwrap().is_none()); +} + +#[tokio::test] +async fn test_leader_completeness_property() { + let ctx = TestContext::new( + PersistenceStrategy::DiskFirst, + FlushPolicy::Immediate, + "test_leader_completeness", + ); + + // Leader writes entries + ctx.append_entries(1, 10, 1).await; + + // Simulate majority replication scenario: + // Leader has: [1,2,3,4,5,6,7,8,9,10] + // Peer1 matched: 7, Peer2 matched: 6, Peer3 matched: 5 + // With leader's last_entry_id (10), the sorted array is: [10, 7, 6, 5] + // Majority index (median) at position 2 is: 6 + let commit_index = ctx.raft_log.calculate_majority_matched_index( + 1, // current_term + 0, // commit_index (previous) + vec![7, 6, 5], // peer match indexes + ); + + // FIXED: Expected result is 6, not 7 + // Because: sorted [10, 7, 6, 5], majority_index = peers[len/2] = peers[2] = 6 + assert_eq!(commit_index, Some(6), "Majority commit index should be 6"); + + // New leader must have all committed entries + for i in 1..=6 { + assert!( + ctx.raft_log.entry(i).unwrap().is_some(), + "Entry {i} should exist", + ); + } +} + +// ========== CONCURRENCY TESTS ========== + +#[tokio::test] +async fn test_concurrent_append_and_purge() { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 100, + interval_ms: 50, + }, + "test_concurrent_append_purge", + ); + + // Pre-populate + ctx.append_entries(1, 1000, 1).await; + + let mut handles = vec![]; + + // Concurrent appends + for i in 0..5 { + let log = ctx.raft_log.clone(); + handles.push(tokio::spawn(async move { + let start = 1000 + i * 100 + 1; + for j in 0..100 { + log.append_entries(vec![Entry { + index: start + j, + term: 2, + payload: None, + }]) + .await + .unwrap(); + } + })); + } + + // Concurrent purges + for cutoff in [100, 200, 300, 400, 500] { + let log = ctx.raft_log.clone(); + handles.push(tokio::spawn(async move { + tokio::time::sleep(Duration::from_millis(10)).await; + log.purge_logs_up_to(LogId { + index: cutoff, + term: 1, + }) + .await + .unwrap(); + })); + } + + join_all(handles).await; + + // Verify consistency + assert!(ctx.raft_log.first_entry_id() > 500); + assert_eq!(ctx.raft_log.last_entry_id(), 1500); +} + +#[tokio::test] +async fn test_term_index_correctness_under_load() { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Immediate, + "test_term_index_under_load", + ); + + // Concurrent writes with different terms + let mut handles = vec![]; + for term in 1..=10 { + let log = ctx.raft_log.clone(); + handles.push(tokio::spawn(async move { + for i in 1..=100 { + log.append_entries(vec![Entry { + index: (term - 1) * 100 + i, + term, + payload: None, + }]) + .await + .unwrap(); + } + })); + } + + join_all(handles).await; + + // Verify term indexes are correct + for term in 1..=10 { + let first = ctx.raft_log.first_index_for_term(term); + let last = ctx.raft_log.last_index_for_term(term); + + assert_eq!(first, Some((term - 1) * 100 + 1)); + assert_eq!(last, Some(term * 100)); + } +} + +// ========== EDGE CASE TESTS ========== + +#[tokio::test] +async fn test_empty_log_operations() { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Immediate, + "test_empty_log", + ); + + assert!(ctx.raft_log.is_empty()); + assert_eq!(ctx.raft_log.first_entry_id(), 0); + assert_eq!(ctx.raft_log.last_entry_id(), 0); + assert!(ctx.raft_log.last_entry().is_none()); + assert_eq!(ctx.raft_log.get_entries_range(1..=10).unwrap().len(), 0); + assert!(ctx.raft_log.first_index_for_term(1).is_none()); + assert!(ctx.raft_log.last_index_for_term(1).is_none()); +} +#[tokio::test] +async fn test_single_entry_operations() { + let ctx = TestContext::new( + PersistenceStrategy::DiskFirst, + FlushPolicy::Immediate, + "test_single_entry", + ); + + ctx.append_entries(1, 1, 1).await; + + assert_eq!(ctx.raft_log.first_entry_id(), 1); + assert_eq!(ctx.raft_log.last_entry_id(), 1); + assert_eq!(ctx.raft_log.first_index_for_term(1), Some(1)); + assert_eq!(ctx.raft_log.last_index_for_term(1), Some(1)); + + // Purge should result in empty log + ctx.raft_log.purge_logs_up_to(LogId { index: 1, term: 1 }).await.unwrap(); + + assert!(ctx.raft_log.is_empty()); +} + +#[tokio::test] +async fn test_gap_handling_in_indexes() { + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Immediate, + "test_gap_handling", + ); + + // Create gaps by selective removal + ctx.append_entries(1, 100, 1).await; + ctx.raft_log.remove_range(25..=75); + + // Verify boundaries are correct + assert_eq!(ctx.raft_log.first_entry_id(), 1); + assert_eq!(ctx.raft_log.last_entry_id(), 100); + + // Verify gaps are handled + assert!(ctx.raft_log.entry(24).unwrap().is_some()); + assert!(ctx.raft_log.entry(25).unwrap().is_none()); + assert!(ctx.raft_log.entry(75).unwrap().is_none()); + assert!(ctx.raft_log.entry(76).unwrap().is_some()); + + // Range queries should skip gaps + let range = ctx.raft_log.get_entries_range(20..=80).unwrap(); + // FIXED: 20-24 (5 entries) + 76-80 (5 entries) = 10 entries total + assert_eq!(range.len(), 10, "Should have 10 entries: 20-24 and 76-80"); + + // Verify content + assert_eq!(range.first().unwrap().index, 20); + assert_eq!(range.last().unwrap().index, 80); +} + +// ========== PERFORMANCE REGRESSION TESTS ========== + +#[tokio::test] +async fn test_read_performance_remains_lockfree() { + // Detect if this test is being run individually (via cargo test ) + let args: Vec = std::env::args().collect(); + let test_name = "test_read_performance_remains_lockfree"; + let is_single_run = args.iter().any(|a| a.contains(test_name)); + + let expected_min = if is_single_run { 10_000.0 } else { 10.0 }; + + let ctx = TestContext::new( + PersistenceStrategy::MemFirst, + FlushPolicy::Batch { + threshold: 1000, + interval_ms: 100, + }, + "test_lockfree_reads", + ); + + // Pre-populate + ctx.append_entries(1, 10000, 1).await; + + // Measure read performance under write load + let log = ctx.raft_log.clone(); + let write_handle = tokio::spawn(async move { + for i in 1..=1000 { + log.append_entries(vec![Entry { + index: 10000 + i, + term: 2, + payload: Some(EntryPayload::command(Bytes::from(vec![0; 1024]))), + }]) + .await + .unwrap(); + tokio::time::sleep(Duration::from_micros(100)).await; + } + }); + + // FIXED: Use saturating_add to prevent overflow and add timeout + let start = Instant::now(); + let mut read_count: u64 = 0; + let timeout = Duration::from_millis(200); + + while !write_handle.is_finished() && start.elapsed() < timeout { + for i in 1..=100 { + let index = (i * 100).min(10000); // Ensure index is valid + let _ = ctx.raft_log.entry(index); + read_count = read_count.saturating_add(1); + } + + // Add small yield to prevent tight loop + tokio::task::yield_now().await; + } + + write_handle.await.unwrap(); + let duration = start.elapsed(); + let reads_per_sec = read_count as f64 / duration.as_secs_f64(); + + println!("Performed {read_count} reads in {duration:?} ({reads_per_sec:.2} reads/sec)",); + + // FIXED: More reasonable performance expectation + assert!( + reads_per_sec > expected_min, + "Read performance degraded: {reads_per_sec:.2} reads/sec (expected > {expected_min:.2})", + ); +} + +#[tokio::test] +async fn test_shutdown_awaits_worker_completion() { + let node_id = 1; + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path().to_path_buf(); + let storage = Arc::new(FileStorageEngine::new(path.clone()).unwrap()); + + let (raft_log, receiver) = + BufferedRaftLog::>::new( + node_id, + PersistenceConfig { + strategy: PersistenceStrategy::MemFirst, + flush_policy: FlushPolicy::Batch { + threshold: 100, + interval_ms: 5000, + }, + max_buffered_entries: 1000, + ..Default::default() + }, + storage, + ); + + let raft_log = raft_log.start(receiver); + + // Append entries to trigger worker activity + for i in 1..=50 { + raft_log + .append_entries(vec![Entry { + index: i, + term: 1, + payload: Some(EntryPayload::command(Bytes::from(vec![0u8; 100]))), + }]) + .await + .unwrap(); + } + + // Force flush to ensure workers have work + raft_log.flush().await.unwrap(); + + // Give workers time to start processing + tokio::time::sleep(Duration::from_millis(50)).await; + + // Trigger shutdown via Drop (which calls shutdown internally) + let shutdown_start = std::time::Instant::now(); + drop(raft_log); + let shutdown_duration = shutdown_start.elapsed(); + + // Verify shutdown completed in reasonable time + assert!( + shutdown_duration < Duration::from_secs(3), + "Shutdown took too long: {shutdown_duration:?}", + ); +} + +#[tokio::test] +async fn test_shutdown_handles_slow_workers() { + let node_id = 1; + + // Use Arc for synchronization + let worker_busy = Arc::new(AtomicBool::new(false)); + let worker_complete = Arc::new(AtomicBool::new(false)); + + // Create storage with controlled persist operations + let mut log_store = MockLogStore::new(); + log_store.expect_last_index().returning(|| 0); + log_store.expect_truncate().returning(|_| Ok(())); + log_store.expect_reset().returning(|| Ok(())); + log_store.expect_flush().returning(|| Ok(())); + + // Clone the Arc values for the mock + let worker_busy_clone = worker_busy.clone(); + let worker_complete_clone = worker_complete.clone(); + + // Simulate persist operations that control timing + log_store.expect_persist_entries().returning(move |entries| { + // Signal that worker is busy + worker_busy_clone.store(true, Ordering::SeqCst); + + // Small initial delay + std::thread::sleep(Duration::from_millis(50)); + + // Wait until allowed to complete or timeout + let start = std::time::Instant::now(); + while !worker_complete_clone.load(Ordering::SeqCst) { + std::thread::sleep(Duration::from_millis(10)); + + // Safety timeout after 3 seconds to prevent test hanging + if start.elapsed() > Duration::from_secs(3) { + break; + } + } + + // Log the entries we're persisting for debugging + println!("Persisting {} entries", entries.len()); + + Ok(()) + }); + + let storage = Arc::new(MockStorageEngine::from(log_store, MockMetaStore::new())); + + let (raft_log, receiver) = + BufferedRaftLog::>::new( + node_id, + PersistenceConfig { + strategy: PersistenceStrategy::DiskFirst, + flush_policy: FlushPolicy::Immediate, + max_buffered_entries: 1000, + flush_workers: 2, // Use multiple workers + ..Default::default() + }, + storage, + ); + + let raft_log = raft_log.start(receiver); + + // Append entries to create work for workers + for i in 1..=10 { + raft_log + .append_entries(vec![Entry { + index: i, + term: 1, + payload: Some(EntryPayload::command(Bytes::from(vec![0u8; 50]))), + }]) + .await + .unwrap(); + } + + // Trigger flush and wait for worker to become busy + raft_log.flush().await.unwrap(); + + // Wait for worker to signal it's busy (with timeout) + let start = std::time::Instant::now(); + while !worker_busy.load(Ordering::SeqCst) { + tokio::time::sleep(Duration::from_millis(10)).await; + if start.elapsed() > Duration::from_secs(2) { + panic!("Workers did not start within expected time"); + } + } + + // Shutdown should wait for slow workers + let shutdown_start = std::time::Instant::now(); + + // Drop triggers shutdown + drop(raft_log); + + // Allow the workers to complete after a controlled delay + // This simulates workers finishing their current task after shutdown is initiated + tokio::time::sleep(Duration::from_millis(200)).await; + worker_complete.store(true, Ordering::SeqCst); + + let shutdown_duration = shutdown_start.elapsed(); + + // Should complete within reasonable timeout window + assert!( + shutdown_duration < Duration::from_secs(5), + "Shutdown exceeded expected duration: {shutdown_duration:?}", + ); + + // Should have waited at least for our controlled delay plus buffer + assert!( + shutdown_duration >= Duration::from_millis(200), + "Shutdown completed too quickly ({shutdown_duration:?}), may not have waited for workers", + ); +} + +#[tokio::test] +async fn test_shutdown_with_multiple_flushes() { + let node_id = 1; + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path().to_path_buf(); + let storage = Arc::new(FileStorageEngine::new(path.clone()).unwrap()); + + let (raft_log, receiver) = + BufferedRaftLog::>::new( + node_id, + PersistenceConfig { + strategy: PersistenceStrategy::MemFirst, + flush_policy: FlushPolicy::Batch { + threshold: 5, + interval_ms: 100, + }, + max_buffered_entries: 1000, + ..Default::default() + }, + storage, + ); + + let raft_log = raft_log.start(receiver); + + // Create multiple flush operations + for batch in 0..5 { + for i in 1..=10 { + let index = batch * 10 + i; + raft_log + .append_entries(vec![Entry { + index, + term: 1, + payload: Some(EntryPayload::command(Bytes::from(vec![0u8; 100]))), + }]) + .await + .unwrap(); + } + raft_log.flush().await.unwrap(); + } + + // Give workers time to process + tokio::time::sleep(Duration::from_millis(200)).await; + + // Shutdown should handle all pending work + let shutdown_start = std::time::Instant::now(); + drop(raft_log); + let shutdown_duration = shutdown_start.elapsed(); + + assert!( + shutdown_duration < Duration::from_secs(3), + "Shutdown with multiple flushes took too long" + ); +} + +#[cfg(test)] +mod durable_index_test { + use tempfile::tempdir; + use tokio::sync::oneshot; + + use crate::{FlushPolicy, PersistenceConfig, PersistenceStrategy}; + + use super::*; + use std::sync::atomic::Ordering; + + #[tokio::test] + async fn test_durable_index_with_non_contiguous_entries() { + // Create a test storage backend + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path().to_path_buf(); + let storage = Arc::new(FileStorageEngine::new(path.clone()).unwrap()); + + // Create buffered raft log with MemFirst strategy for easier testing + let (raft_log, receiver) = + BufferedRaftLog::>::new( + 1, + PersistenceConfig { + strategy: PersistenceStrategy::MemFirst, + flush_policy: FlushPolicy::Immediate, + ..Default::default() + }, + storage, + ); + + let raft_log = raft_log.start(receiver); + + // Create a test waiter for index 10 + let (tx, mut rx) = oneshot::channel(); + let waiter_index = 10; + raft_log.waiters.entry(waiter_index).or_default().push(tx); + + // Create entries with non-contiguous indexes: 4, 7, 8, 10 + let entries = vec![ + Entry { + index: 4, + ..Default::default() + }, + Entry { + index: 7, + ..Default::default() + }, + Entry { + index: 8, + ..Default::default() + }, + Entry { + index: 10, + ..Default::default() + }, + ]; + + // Manually add entries to memory store + for entry in &entries { + raft_log.entries.insert(entry.index, entry.clone()); + } + + // Set initial durable_index to 3 + raft_log.durable_index.store(3, Ordering::Release); + + // Test the buggy implementation: directly call process_flush with non-contiguous indexes + let indexes = vec![4, 7, 8, 10]; + raft_log.process_flush(&indexes).await.unwrap(); + + // Check if waiter was notified (it should NOT be in the correct implementation) + let notification_result = rx.try_recv(); + + assert!( + notification_result.is_err(), + "Waiter was correctly NOT notified because entries are non-contiguous" + ); + + // Check if durable_index was updated correctly to only include contiguous entries + let final_durable_index = raft_log.durable_index.load(Ordering::Acquire); + assert_eq!( + final_durable_index, 4, + "Durable index was correctly updated only to the last contiguous entry (4)" + ); + } +} diff --git a/d-engine-runtime/tests/components/election_handler_test.rs b/d-engine-runtime/tests/components/election_handler_test.rs new file mode 100644 index 00000000..79876feb --- /dev/null +++ b/d-engine-runtime/tests/components/election_handler_test.rs @@ -0,0 +1,682 @@ +use std::sync::Arc; + +use tokio::sync::watch; +use tracing::debug; +use tracing_test::traced_test; + +use crate::ConsensusError; +use crate::ElectionCore; +use crate::ElectionError; +use crate::ElectionHandler; +use crate::Error; +use crate::MockMembership; +use crate::MockRaftLog; +use crate::MockTransport; +use crate::VoteResult; +use crate::alias::ROF; +use crate::alias::TROF; +use crate::test_utils::MockTypeConfig; +use crate::test_utils::mock_membership; +use crate::test_utils::setup_raft_components; +use d_engine_proto::common::LogId; +use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::server::cluster::NodeMeta; +use d_engine_proto::server::election::VoteRequest; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::election::VotedFor; + +struct TestConext { + election_handler: ElectionHandler, + raft_log_mock: ROF, +} +async fn setup() -> TestConext { + let election_handler = ElectionHandler::::new(1); + + let raft_log_mock: ROF = MockRaftLog::new(); + + TestConext { + election_handler, + raft_log_mock, + } +} + +/// # Case 1: Receive election failed error if there is zero peers +/// +/// ## Validation Criterias: +/// 1. Receive ElectionError::QuorumFailure error +#[tokio::test] +#[traced_test] +async fn test_broadcast_vote_requests_case1() { + // 1. Create a ElectionHandler instance + let (_graceful_tx, _graceful_rx) = watch::channel(()); + let ctx = setup_raft_components("/tmp/test_broadcast_vote_requests_case1", None, false); + + let election_handler = ElectionHandler::::new(1); + + // 2. + let term = 1; + let mut raft_log_mock: ROF = MockRaftLog::new(); + raft_log_mock + .expect_last_log_id() + .times(0) + .returning(|| Some(LogId { index: 1, term: 1 })); + + let mut transport_mock: TROF = MockTransport::new(); + transport_mock.expect_send_vote_requests().times(0).returning(|_, _, _| { + Ok(VoteResult { + peer_ids: vec![2].into_iter().collect(), + responses: vec![Ok(VoteResponse { + term: 1, + vote_granted: false, + last_log_index: 1, + last_log_term: 1, + })], + }) + }); + + let err = election_handler + .broadcast_vote_requests( + term, + Arc::new(mock_membership()), + &Arc::new(raft_log_mock), + &Arc::new(transport_mock), + &ctx.arc_node_config, + ) + .await + .unwrap_err(); + + debug!(?err); + + assert!(matches!( + err, + Error::Consensus(ConsensusError::Election( + ElectionError::NoVotingMemberFound { candidate_id: _ } + )) + )); +} + +/// # Case 2: Test failed to receive majority peers' failed vote +/// +/// ## Validation criterias: +/// 1. Test should receive Err(ElectionError::LogConflict) +#[tokio::test] +#[traced_test] +async fn test_broadcast_vote_requests_case2() { + let (_graceful_tx, _graceful_rx) = watch::channel(()); + let ctx = setup_raft_components("/tmp/test_broadcast_vote_requests_case2", None, false); + let mut test_context = setup().await; + test_context + .raft_log_mock + .expect_last_log_id() + .times(0) + .returning(|| Some(LogId { index: 1, term: 1 })); + + let mut transport_mock: TROF = MockTransport::new(); + transport_mock.expect_send_vote_requests().times(0).returning(|_, _, _| { + Ok(VoteResult { + peer_ids: vec![2].into_iter().collect(), + responses: vec![Ok(VoteResponse { + term: 1, + vote_granted: false, + last_log_index: 1, + last_log_term: 1, + })], + }) + }); + let term = 1; + + let e = test_context + .election_handler + .broadcast_vote_requests( + term, + Arc::new(mock_membership()), + &Arc::new(test_context.raft_log_mock), + &Arc::new(transport_mock), + &ctx.arc_node_config, + ) + .await + .unwrap_err(); + + if let Error::Consensus(ConsensusError::Election(ElectionError::LogConflict { + index, + expected_term, + actual_term, + })) = e + { + assert_eq!(index, 1); + assert_eq!(actual_term, 1); + assert_eq!(expected_term, 1); + } +} +/// # Case 3: Test after receiving majority peers' success vote +/// +/// ## Validation criterias: +/// 1. Test should receive Ok(()) +#[tokio::test] +#[traced_test] +async fn test_broadcast_vote_requests_case3() { + let (_graceful_tx, _graceful_rx) = watch::channel(()); + let ctx = setup_raft_components("/tmp/test_broadcast_vote_requests_case3", None, false); + let mut test_context = setup().await; + test_context + .raft_log_mock + .expect_last_log_id() + .times(1) + .returning(|| Some(LogId { index: 1, term: 1 })); + let mut transport_mock: TROF = MockTransport::new(); + transport_mock.expect_send_vote_requests().times(1).returning(|_, _, _| { + Ok(VoteResult { + peer_ids: vec![2].into_iter().collect(), + responses: vec![Ok(VoteResponse { + term: 1, + vote_granted: true, + last_log_index: 1, + last_log_term: 1, + })], + }) + }); + + let term = 1; + let mut membership = MockMembership::new(); + membership.expect_voters().returning(move || { + vec![NodeMeta { + id: 2, + address: "http://127.0.0.1:55001".to_string(), + role: Follower.into(), + status: NodeStatus::Active.into(), + }] + }); + let r = test_context + .election_handler + .broadcast_vote_requests( + term, + Arc::new(membership), + &Arc::new(test_context.raft_log_mock), + &Arc::new(transport_mock), + &ctx.arc_node_config, + ) + .await; + debug!("test_broadcast_vote_requests_case3: {:?}", &r); + assert!(r.is_ok()) +} + +/// # Case 4: Test if vote response returns higher last_log_term +// ## Setup: +// 1. prepare one peers, which returns failed response +// 2. Peer1 returns higher term of last log index, +// +// ## Criterias: +// 1. return Err(ElectionError::HigherTerm) +#[tokio::test] +#[traced_test] +async fn test_broadcast_vote_requests_case4() { + let (_graceful_tx, _graceful_rx) = watch::channel(()); + let ctx = setup_raft_components("/tmp/test_broadcast_vote_requests_case4", None, false); + let test_context = setup().await; + + let my_last_log_term = 3; + let transport_mock: TROF = MockTransport::new(); + + let e = test_context + .election_handler + .broadcast_vote_requests( + my_last_log_term, + Arc::new(mock_membership()), + &Arc::new(test_context.raft_log_mock), + &Arc::new(transport_mock), + &ctx.arc_node_config, + ) + .await + .unwrap_err(); + + debug!("test_broadcast_vote_requests_case4: {:?}", &e); + + if let Error::Consensus(ConsensusError::Election(ElectionError::HigherTerm(higher_term))) = e { + assert_eq!(higher_term, my_last_log_term + 1); + } +} + +/// # Case 5: Test if vote response returns higher last_log_index +// ## Setup: +// 1. prepare one peers, which returns failed response +// 2. Peer1 returns last log term is the same as candidate one, but last log index is higher than +// candidate last log index, +// +// ## Criterias: +// 1. return Err(ElectionError::LogConflict) +#[tokio::test] +#[traced_test] +async fn test_broadcast_vote_requests_case5() { + let (_graceful_tx, _graceful_rx) = watch::channel(()); + let ctx = setup_raft_components("/tmp/test_broadcast_vote_requests_case5", None, false); + let test_context = setup().await; + let my_last_log_index = 1; + let my_last_log_term = 3; + + let transport_mock: TROF = MockTransport::new(); + + let e = test_context + .election_handler + .broadcast_vote_requests( + my_last_log_term, + Arc::new(mock_membership()), + &Arc::new(test_context.raft_log_mock), + &Arc::new(transport_mock), + &ctx.arc_node_config, + ) + .await + .unwrap_err(); + + if let Error::Consensus(ConsensusError::Election(ElectionError::LogConflict { + index, + expected_term, + actual_term, + })) = e + { + assert_eq!(index, my_last_log_index); + assert_eq!(expected_term, my_last_log_term); + assert_eq!(actual_term, my_last_log_term); + } +} + +/// # Case 1: Test if vote request is legal +/// +/// ## Validation criterias: +/// 1. Switch back to Follower +/// 2. Returns with Ok(state_update) +/// - step_to_follower = true +/// - voted_for_option = Some(x) +/// - term_update = Some(y) +#[tokio::test] +#[traced_test] +async fn test_handle_vote_request_case1() { + let (_graceful_tx, _graceful_rx) = watch::channel(()); + let mut test_context = setup().await; + test_context + .raft_log_mock + .expect_last_log_id() + .times(1) + .returning(|| Some(LogId { index: 1, term: 1 })); + + let current_term = 1; + let last_log_index = 1; + let last_log_term = 1; + + let request_term = current_term + 1; + let vote_request = VoteRequest { + term: request_term, + candidate_id: 1, + last_log_index: last_log_index + 1, + last_log_term, + }; + let voted_for_option = None; + assert!( + test_context + .election_handler + .handle_vote_request( + vote_request, + current_term, + voted_for_option, + &Arc::new(test_context.raft_log_mock), + ) + .await + .is_ok_and(move |state_update| state_update.new_voted_for.is_some() + && state_update.term_update.unwrap() == request_term) + ); +} + +/// # Case 2: Test if vote request is illegal +/// (current_term >= VoteRequest Term) +/// +/// ## Validation criterias: +/// 1. role_rx.try_recv with None +/// 2. Returns with Ok(None) +#[tokio::test] +#[traced_test] +async fn test_handle_vote_request_case2() { + let (_graceful_tx, _graceful_rx) = watch::channel(()); + let mut test_context = setup().await; + test_context + .raft_log_mock + .expect_last_log_id() + .times(1) + .returning(|| Some(LogId { index: 1, term: 1 })); + + let current_term = 10; + let last_log_index = 1; + let last_log_term = 1; + + let vote_request = VoteRequest { + term: current_term - 1, + candidate_id: 1, + last_log_index: last_log_index + 1, + last_log_term, + }; + let voted_for_option = None; + + let state_update = test_context + .election_handler + .handle_vote_request( + vote_request, + current_term, + voted_for_option, + &Arc::new(test_context.raft_log_mock), + ) + .await + .unwrap(); + assert!(state_update.new_voted_for.is_none()); +} + +/// Case 1.1: Term and local log index compare +/// - current_term >= VoteRequest Term +/// +/// Criterias: +/// - false +#[tokio::test] +#[traced_test] +async fn test_check_vote_request_is_legal_case_1_1() { + // 1. Prepare RaftContext mock + let context = setup_raft_components( + "/tmp/test_check_vote_request_is_legal_case_1_1", + None, + false, + ); + let election_controller = context.election_handler; + + let vote_request = VoteRequest { + term: 1, + candidate_id: 1, + last_log_index: 1, + last_log_term: 1, + }; + let current_term = 1; + let last_log_index = 1; + let last_log_term = 1; + let voted_for_id = 1; + let voted_for_term = 1; + + assert!(!election_controller.check_vote_request_is_legal( + &vote_request, + current_term, + last_log_index, + last_log_term, + Some(VotedFor { + voted_for_id, + voted_for_term + }) + )); + let current_term = 2; + assert!(!election_controller.check_vote_request_is_legal( + &vote_request, + current_term, + last_log_index, + last_log_term, + Some(VotedFor { + voted_for_id, + voted_for_term + }) + )); +} + +/// Case 1.2: Term and local log index compare +/// - current_term <= VoteRequest Term +/// - request.last_log_term <= last_log_term +/// +/// Criterias: +/// - false +#[tokio::test] +#[traced_test] +async fn test_check_vote_request_is_legal_case_1_2() { + // 1. Prepare RaftContext mock + let context = setup_raft_components( + "/tmp/test_check_vote_request_is_legal_case_1_2", + None, + false, + ); + let election_controller = context.election_handler; + let current_term = 1; + + let vote_request = VoteRequest { + term: current_term, + candidate_id: 1, + last_log_index: 1, + last_log_term: 1, + }; + let last_log_index = 1; + let voted_for_id = 1; + let voted_for_term = 1; + + let last_log_term = 2; + assert!(!election_controller.check_vote_request_is_legal( + &vote_request, + current_term, + last_log_index, + last_log_term, + Some(VotedFor { + voted_for_id, + voted_for_term + }) + )); + + let last_log_term = 1; + assert!(!election_controller.check_vote_request_is_legal( + &vote_request, + current_term, + last_log_index, + last_log_term, + Some(VotedFor { + voted_for_id, + voted_for_term + }) + )); +} + +/// Case 1.3: Term and local log index compare +/// - current_term <= VoteRequest Term +/// - request.last_log_term = last_log_term +/// - request.last_log_index > last_log_index +/// +/// Criterias: +/// - true +#[tokio::test] +#[traced_test] +async fn test_check_vote_request_is_legal_case_1_3() { + // 1. Prepare RaftContext mock + let context = setup_raft_components( + "/tmp/test_check_vote_request_is_legal_case_1_3", + None, + false, + ); + let election_controller = context.election_handler; + let current_term = 1; + let last_log_index = 1; + let last_log_term = 1; + + let vote_request = VoteRequest { + term: current_term, + candidate_id: 1, + last_log_index: last_log_index + 1, + last_log_term, + }; + assert!(election_controller.check_vote_request_is_legal( + &vote_request, + current_term, + last_log_index, + last_log_term, + None, + )); +} + +/// Case 1.4: Term and local log index compare +/// - current_term <= VoteRequest Term +/// - request.last_log_term = last_log_term +/// - request.last_log_index < last_log_index +/// +/// Criterias: +/// - false +#[tokio::test] +#[traced_test] +async fn test_check_vote_request_is_legal_case_1_4() { + // 1. Prepare RaftContext mock + let context = setup_raft_components( + "/tmp/test_check_vote_request_is_legal_case_1_4", + None, + false, + ); + let election_controller = context.election_handler; + let current_term = 1; + + let vote_request = VoteRequest { + term: current_term, + candidate_id: 1, + last_log_index: 1, + last_log_term: 1, + }; + let last_log_index = 2; + let voted_for_id = 1; + let voted_for_term = 1; + + let last_log_term = 1; + assert!(!election_controller.check_vote_request_is_legal( + &vote_request, + current_term, + last_log_index, + last_log_term, + Some(VotedFor { + voted_for_id, + voted_for_term + }) + )); +} + +/// Case 2.1: granted_vote checking +/// - current_term <= VoteRequest Term +/// - request.last_log_term = last_log_term +/// - request.last_log_index >= last_log_index +/// - current node has granted vote +/// +/// Criterias: +/// - false +#[tokio::test] +#[traced_test] +async fn test_check_vote_request_is_legal_case_2_1() { + // 1. Prepare RaftContext mock + let context = setup_raft_components( + "/tmp/test_check_vote_request_is_legal_case_2_1", + None, + false, + ); + let election_controller = context.election_handler; + let current_term = 1; + + let vote_request = VoteRequest { + term: current_term, + candidate_id: 1, + last_log_index: 3, + last_log_term: 1, + }; + let last_log_index = 2; + let last_log_term = 1; + + let voted_for_id = 3; + let voted_for_term = 1; + + assert!(!election_controller.check_vote_request_is_legal( + &vote_request, + current_term, + last_log_index, + last_log_term, + Some(VotedFor { + voted_for_id, + voted_for_term + }) + )); +} + +/// Case 2.2: granted_vote checking +/// - current_term <= VoteRequest Term +/// - request.last_log_term = last_log_term +/// - request.last_log_index >= last_log_index +/// - current node has granted vote, and the granted vote term is bigger than the request one. +/// +/// Criterias: +/// - false +#[tokio::test] +#[traced_test] +async fn test_check_vote_request_is_legal_case_2_2() { + // 1. Prepare RaftContext mock + let context = setup_raft_components( + "/tmp/test_check_vote_request_is_legal_case_2_2", + None, + false, + ); + let election_controller = context.election_handler; + let current_term = 1; + + let vote_request = VoteRequest { + term: current_term, + candidate_id: 1, + last_log_index: 3, + last_log_term: 1, + }; + let last_log_index = 2; + let last_log_term = 1; + + let voted_for_id = 1; + let voted_for_term = 10; + + assert!(!election_controller.check_vote_request_is_legal( + &vote_request, + current_term, + last_log_index, + last_log_term, + Some(VotedFor { + voted_for_id, + voted_for_term + }) + )); +} +/// Case 2.3: granted_vote checking +/// - current_term <= VoteRequest Term +/// - request.last_log_term = last_log_term +/// - request.last_log_index >= last_log_index +/// - current node has granted vote, and the granted vote term is bigger than the request one. +/// +/// Criterias: +/// - true +#[tokio::test] +#[traced_test] +async fn test_check_vote_request_is_legal_case_2_3() { + // 1. Prepare RaftContext mock + let context = setup_raft_components( + "/tmp/test_check_vote_request_is_legal_case_2_3", + None, + false, + ); + let election_controller = context.election_handler; + let current_term = 10; + + let vote_request = VoteRequest { + term: current_term, + candidate_id: 1, + last_log_index: 3, + last_log_term: 1, + }; + let last_log_index = 2; + let last_log_term = 1; + + let voted_for_id = 1; + let voted_for_term = 1; + + assert!(election_controller.check_vote_request_is_legal( + &vote_request, + current_term, + last_log_index, + last_log_term, + Some(VotedFor { + voted_for_id, + voted_for_term + }) + )); +} diff --git a/d-engine-runtime/tests/components/mod.rs b/d-engine-runtime/tests/components/mod.rs new file mode 100644 index 00000000..e05c55e1 --- /dev/null +++ b/d-engine-runtime/tests/components/mod.rs @@ -0,0 +1,2 @@ +mod buffered_raft_log_test; +mod raft_log_test; diff --git a/d-engine-runtime/tests/integration_test.rs b/d-engine-runtime/tests/integration_test.rs new file mode 100644 index 00000000..2758dbf6 --- /dev/null +++ b/d-engine-runtime/tests/integration_test.rs @@ -0,0 +1 @@ +mod integration_test; From b1f478470058068e08f726a2fbbab3f23786093d Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Fri, 24 Oct 2025 11:26:24 +0800 Subject: [PATCH 08/26] feat #167: d-engine-core test works --- d-engine-core/Cargo.toml | 2 - .../src/election/election_handler.rs | 2 +- d-engine-core/src/election/mod.rs | 4 +- d-engine-core/src/lib.rs | 11 ++-- d-engine-core/src/raft_role/mod.rs | 14 +---- d-engine-core/src/replication/mod.rs | 2 - .../default_state_machine_handler_test.rs | 4 +- .../src/state_machine_handler/mod.rs | 5 +- d-engine-core/src/storage/state_machine.rs | 1 - d-engine-core/src/test_utils/common.rs | 63 +++---------------- d-engine-core/src/test_utils/mod.rs | 13 +++- .../test_utils/{integration => }/snapshot.rs | 6 +- d-engine-core/src/utils/convert.rs | 9 ++- d-engine-core/src/utils/file_io.rs | 4 +- d-engine-core/src/utils/mod.rs | 9 +-- .../src/test_utils/integration/mod.rs | 0 .../{ => election}/election_handler_test.rs | 0 .../raft_role/candidate_state_test.rs | 0 .../raft_role/follower_state_test.rs | 0 .../raft_role/leader_state_test.rs | 0 .../raft_role/learner_state_test.rs | 0 .../replication/replication_handler_test.rs | 0 .../tests}/raft_test.rs | 0 23 files changed, 43 insertions(+), 106 deletions(-) rename d-engine-core/src/test_utils/{integration => }/snapshot.rs (100%) rename {d-engine-core => d-engine-runtime}/src/test_utils/integration/mod.rs (100%) rename d-engine-runtime/tests/components/{ => election}/election_handler_test.rs (100%) rename {d-engine-core/src => d-engine-runtime/tests/components}/raft_role/candidate_state_test.rs (100%) rename {d-engine-core/src => d-engine-runtime/tests/components}/raft_role/follower_state_test.rs (100%) rename {d-engine-core/src => d-engine-runtime/tests/components}/raft_role/leader_state_test.rs (100%) rename {d-engine-core/src => d-engine-runtime/tests/components}/raft_role/learner_state_test.rs (100%) rename {d-engine-core/src => d-engine-runtime/tests/components}/replication/replication_handler_test.rs (100%) rename {d-engine-core/src => d-engine-runtime/tests}/raft_test.rs (100%) diff --git a/d-engine-core/Cargo.toml b/d-engine-core/Cargo.toml index aca1a157..319e1277 100644 --- a/d-engine-core/Cargo.toml +++ b/d-engine-core/Cargo.toml @@ -3,8 +3,6 @@ name = "d-engine-core" version = "0.1.0" edition = "2024" -[features] -test-utils = [] [dependencies] d-engine-proto = { workspace = true } diff --git a/d-engine-core/src/election/election_handler.rs b/d-engine-core/src/election/election_handler.rs index c60303a8..963b26aa 100644 --- a/d-engine-core/src/election/election_handler.rs +++ b/d-engine-core/src/election/election_handler.rs @@ -316,7 +316,7 @@ impl ElectionHandler where T: TypeConfig, { - pub(crate) fn new(my_id: u32) -> Self { + pub fn new(my_id: u32) -> Self { Self { my_id, _phantom: PhantomData, diff --git a/d-engine-core/src/election/mod.rs b/d-engine-core/src/election/mod.rs index 9f48cf08..8f8e502b 100644 --- a/d-engine-core/src/election/mod.rs +++ b/d-engine-core/src/election/mod.rs @@ -7,8 +7,8 @@ mod election_handler; pub use election_handler::*; -#[cfg(test)] -mod election_handler_test; +// #[cfg(test)] +// mod election_handler_test; use std::sync::Arc; diff --git a/d-engine-core/src/lib.rs b/d-engine-core/src/lib.rs index 1c606957..1d7ab21d 100644 --- a/d-engine-core/src/lib.rs +++ b/d-engine-core/src/lib.rs @@ -25,8 +25,11 @@ pub use network::*; pub use raft::*; pub use storage::*; +pub use election::*; +pub use replication::*; +pub use state_machine_handler::*; + pub(crate) use commit_handler::*; -pub(crate) use election::*; pub(crate) use maybe_clone_oneshot::*; pub(crate) use purge::*; pub(crate) use raft_context::*; @@ -36,18 +39,14 @@ pub use raft_role::*; #[doc(hidden)] pub use utils::*; -pub(crate) use replication::*; -pub(crate) use state_machine_handler::*; pub(crate) use timer::*; #[doc(hidden)] pub use type_config::*; #[cfg(test)] mod raft_oneshot_test; -#[cfg(test)] -mod raft_test; -#[cfg(any(test, feature = "test-utils"))] +#[cfg(test)] pub mod test_utils; /// In raft, during any Leader to Peer communication, diff --git a/d-engine-core/src/raft_role/mod.rs b/d-engine-core/src/raft_role/mod.rs index 2f275f2a..2803490e 100644 --- a/d-engine-core/src/raft_role/mod.rs +++ b/d-engine-core/src/raft_role/mod.rs @@ -4,17 +4,6 @@ pub mod leader_state; pub mod learner_state; pub mod role_state; -#[cfg(test)] -mod candidate_state_test; -#[cfg(test)] -mod follower_state_test; -#[cfg(test)] -mod leader_state_test; -#[cfg(test)] -mod learner_state_test; - -use std::collections::HashMap; - use candidate_state::CandidateState; use follower_state::FollowerState; use leader_state::LeaderState; @@ -25,6 +14,7 @@ use serde::Deserializer; use serde::Serialize; use serde::Serializer; use serde::ser::SerializeStruct; +use std::collections::HashMap; use tokio::sync::mpsc; use tokio::time::Instant; use tracing::debug; @@ -296,7 +286,7 @@ impl RaftRole { } #[cfg(test)] - pub(crate) fn follower_role_i32() -> i32 { + pub fn follower_role_i32() -> i32 { 0 } diff --git a/d-engine-core/src/replication/mod.rs b/d-engine-core/src/replication/mod.rs index 02a97e42..f636c72a 100644 --- a/d-engine-core/src/replication/mod.rs +++ b/d-engine-core/src/replication/mod.rs @@ -12,8 +12,6 @@ pub use replication_handler::*; #[cfg(test)] mod batch_buffer_test; -#[cfg(test)] -mod replication_handler_test; // Client Request Extension Definition // ----------------------------------------------------------------------------- diff --git a/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs index 567d5e41..e861a252 100644 --- a/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs +++ b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs @@ -1120,7 +1120,7 @@ async fn test_cleanup_snapshot_case1() { // Verify files are compressed for version in &[2, 3] { let path = temp_dir.path().join(format!( - "{}/{}-1.tar.gz", + "{}{}-1.tar.gz", config.snapshots_dir_prefix, version )); assert!(path.is_file(), "Snapshot file not found: {path:?}",); @@ -1504,7 +1504,7 @@ async fn create_test_files( snapshots_dir_prefix: &str, ) { for id in ids { - let file_name = format!("{}{id}-1.tar.gz", snapshots_dir_prefix); + let file_name = format!("{snapshots_dir_prefix}{id}-1.tar.gz",); let path = temp_dir.path().join(file_name); debug!(?path, "create_test_files"); let mut file = File::create(&path).await.unwrap(); diff --git a/d-engine-core/src/state_machine_handler/mod.rs b/d-engine-core/src/state_machine_handler/mod.rs index bcedf98c..c3cae899 100644 --- a/d-engine-core/src/state_machine_handler/mod.rs +++ b/d-engine-core/src/state_machine_handler/mod.rs @@ -36,10 +36,11 @@ mod snapshot_assembler; mod snapshot_guard; mod snapshot_policy; -pub(crate) use default_state_machine_handler::*; +pub use default_state_machine_handler::*; +pub use snapshot_policy::*; + pub(crate) use snapshot_assembler::*; pub(crate) use snapshot_guard::*; -pub(crate) use snapshot_policy::*; #[cfg(test)] mod default_state_machine_handler_test; diff --git a/d-engine-core/src/storage/state_machine.rs b/d-engine-core/src/storage/state_machine.rs index 94e58de0..d6e34cb6 100644 --- a/d-engine-core/src/storage/state_machine.rs +++ b/d-engine-core/src/storage/state_machine.rs @@ -5,7 +5,6 @@ use bytes::Bytes; use mockall::automock; use tonic::async_trait; -use crate::ConvertError; use crate::Error; use d_engine_proto::common::Entry; use d_engine_proto::common::LogId; diff --git a/d-engine-core/src/test_utils/common.rs b/d-engine-core/src/test_utils/common.rs index ff501814..7ceaf277 100644 --- a/d-engine-core/src/test_utils/common.rs +++ b/d-engine-core/src/test_utils/common.rs @@ -1,24 +1,16 @@ -use std::ops::RangeInclusive; -use std::path::PathBuf; -use std::sync::Arc; -use std::time::Duration; - -use bytes::Bytes; -use bytes::BytesMut; -use prost::Message; - -use crate::FileStorageEngine; -use crate::MockStateMachine; -use crate::RaftLog; -use crate::RaftTypeConfig; use crate::SnapshotConfig; -use crate::alias::ROF; use crate::convert::safe_kv_bytes; +use bytes::Bytes; +use bytes::BytesMut; use d_engine_proto::client::WriteCommand; use d_engine_proto::common::AddNode; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; use d_engine_proto::common::membership_change::Change; +use prost::Message; +use std::ops::RangeInclusive; +use std::path::PathBuf; +use std::time::Duration; pub fn create_mixed_entries() -> Vec { let config_entry = Entry { @@ -51,7 +43,7 @@ pub fn create_config_entries() -> Vec { vec![entry] } -pub(crate) fn generate_insert_commands(ids: Vec) -> Bytes { +pub fn generate_insert_commands(ids: Vec) -> Bytes { let mut buffer = BytesMut::new(); for id in ids { @@ -62,7 +54,7 @@ pub(crate) fn generate_insert_commands(ids: Vec) -> Bytes { buffer.freeze() } -pub(crate) fn generate_delete_commands(range: RangeInclusive) -> Bytes { +pub fn generate_delete_commands(range: RangeInclusive) -> Bytes { let mut buffer = BytesMut::new(); for id in range { @@ -72,45 +64,6 @@ pub(crate) fn generate_delete_commands(range: RangeInclusive) -> Bytes { buffer.freeze() } -///Dependes on external id to specify the local log entry index. -/// If duplicated ids are specified, then the only one entry will be inserted. -pub(crate) async fn simulate_insert_command( - raft_log: &Arc>>, - ids: Vec, - term: u64, -) { - let mut entries = Vec::new(); - for id in ids { - let log = Entry { - index: raft_log.pre_allocate_raft_logs_next_index(), - term, - payload: Some(EntryPayload::command(generate_insert_commands(vec![id]))), - }; - entries.push(log); - } - raft_log.insert_batch(entries).await.unwrap(); - raft_log.flush().await.unwrap(); -} - -#[allow(dead_code)] -pub(crate) async fn simulate_delete_command( - raft_log: &Arc>>, - id_range: RangeInclusive, - term: u64, -) { - let mut entries = Vec::new(); - for id in id_range { - let log = Entry { - index: raft_log.pre_allocate_raft_logs_next_index(), - term, - payload: Some(EntryPayload::command(generate_delete_commands(id..=id))), - }; - entries.push(log); - } - if let Err(e) = raft_log.insert_batch(entries).await { - panic!("error: {e:?}"); - } -} pub fn snapshot_config(snapshots_dir: PathBuf) -> SnapshotConfig { SnapshotConfig { diff --git a/d-engine-core/src/test_utils/mod.rs b/d-engine-core/src/test_utils/mod.rs index b6a12c35..8a6fd8ba 100644 --- a/d-engine-core/src/test_utils/mod.rs +++ b/d-engine-core/src/test_utils/mod.rs @@ -2,12 +2,19 @@ //! tests and integrations tests mod common; mod entry_builder; -mod integration; mod mock; -pub mod mock_type_config; +mod snapshot; +pub(crate) use snapshot::*; +pub mod mock_type_config; pub use common::*; pub use entry_builder::*; -pub use integration::*; pub use mock::*; pub use mock_type_config::*; + +pub fn node_config(db_path: &str) -> crate::RaftNodeConfig { + let mut s = + crate::RaftNodeConfig::new().expect("RaftNodeConfig should be inited successfully."); + s.cluster.db_root_dir = std::path::PathBuf::from(db_path); + s +} diff --git a/d-engine-core/src/test_utils/integration/snapshot.rs b/d-engine-core/src/test_utils/snapshot.rs similarity index 100% rename from d-engine-core/src/test_utils/integration/snapshot.rs rename to d-engine-core/src/test_utils/snapshot.rs index 8c0de680..a54a350e 100644 --- a/d-engine-core/src/test_utils/integration/snapshot.rs +++ b/d-engine-core/src/test_utils/snapshot.rs @@ -4,10 +4,10 @@ use bytes::BufMut; use bytes::Bytes; use bytes::BytesMut; use crc32fast::Hasher; -use futures::stream; -use futures::stream::BoxStream; use futures::StreamExt; use futures::TryStreamExt; +use futures::stream; +use futures::stream::BoxStream; use http_body::Frame; use http_body_util::BodyExt; use http_body_util::StreamBody; @@ -19,9 +19,9 @@ use tonic::Code; use tonic::Status; use tracing::debug; -use crate::stream::GrpcStreamDecoder; use crate::NetworkError; use crate::Result; +use crate::stream::GrpcStreamDecoder; use d_engine_proto::common::LogId; use d_engine_proto::server::storage::SnapshotChunk; use d_engine_proto::server::storage::SnapshotMetadata; diff --git a/d-engine-core/src/utils/convert.rs b/d-engine-core/src/utils/convert.rs index ad86a04a..321b35ed 100644 --- a/d-engine-core/src/utils/convert.rs +++ b/d-engine-core/src/utils/convert.rs @@ -1,10 +1,9 @@ +use bytes::Bytes; +use prost::Message; use std::hash::DefaultHasher; use std::hash::Hash; use std::hash::Hasher; -use bytes::Bytes; -use prost::Message; - use crate::ConvertError; use crate::Result; @@ -27,8 +26,8 @@ pub fn safe_kv_bytes(key: u64) -> Bytes { /// Converts a `u64` to an 8-byte array in big-endian byte order. /// /// # Examples -/// ``` -/// use d_engine::convert::safe_kv; +/// ```rust +/// use d_engine_core::convert::safe_kv; /// /// let bytes = safe_kv(0x1234_5678_9ABC_DEF0); /// assert_eq!(bytes, [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]); diff --git a/d-engine-core/src/utils/file_io.rs b/d-engine-core/src/utils/file_io.rs index aa762e0f..207f1ff1 100644 --- a/d-engine-core/src/utils/file_io.rs +++ b/d-engine-core/src/utils/file_io.rs @@ -1,5 +1,5 @@ -use std::fs::create_dir_all; use std::fs::OpenOptions; +use std::fs::create_dir_all; use std::io::ErrorKind; use std::io::Read; use std::path::Path; @@ -198,7 +198,7 @@ pub fn validate_checksum( /// /// # Example /// ```rust -/// use d_engine::file_io::convert_vec_checksum; +/// use d_engine_core::file_io::convert_vec_checksum; /// /// let vec_checksum = vec![0u8; 32]; /// let array_checksum = convert_vec_checksum(vec_checksum).unwrap(); diff --git a/d-engine-core/src/utils/mod.rs b/d-engine-core/src/utils/mod.rs index fda91e2b..aa0f4b85 100644 --- a/d-engine-core/src/utils/mod.rs +++ b/d-engine-core/src/utils/mod.rs @@ -1,16 +1,9 @@ -#[allow(dead_code)] pub mod cluster; -#[allow(dead_code)] pub mod convert; -#[allow(dead_code)] pub mod file_io; -#[allow(dead_code)] pub mod scoped_timer; -#[allow(dead_code)] -pub mod time; - -#[allow(dead_code)] pub mod stream; +pub mod time; #[cfg(test)] mod convert_test; diff --git a/d-engine-core/src/test_utils/integration/mod.rs b/d-engine-runtime/src/test_utils/integration/mod.rs similarity index 100% rename from d-engine-core/src/test_utils/integration/mod.rs rename to d-engine-runtime/src/test_utils/integration/mod.rs diff --git a/d-engine-runtime/tests/components/election_handler_test.rs b/d-engine-runtime/tests/components/election/election_handler_test.rs similarity index 100% rename from d-engine-runtime/tests/components/election_handler_test.rs rename to d-engine-runtime/tests/components/election/election_handler_test.rs diff --git a/d-engine-core/src/raft_role/candidate_state_test.rs b/d-engine-runtime/tests/components/raft_role/candidate_state_test.rs similarity index 100% rename from d-engine-core/src/raft_role/candidate_state_test.rs rename to d-engine-runtime/tests/components/raft_role/candidate_state_test.rs diff --git a/d-engine-core/src/raft_role/follower_state_test.rs b/d-engine-runtime/tests/components/raft_role/follower_state_test.rs similarity index 100% rename from d-engine-core/src/raft_role/follower_state_test.rs rename to d-engine-runtime/tests/components/raft_role/follower_state_test.rs diff --git a/d-engine-core/src/raft_role/leader_state_test.rs b/d-engine-runtime/tests/components/raft_role/leader_state_test.rs similarity index 100% rename from d-engine-core/src/raft_role/leader_state_test.rs rename to d-engine-runtime/tests/components/raft_role/leader_state_test.rs diff --git a/d-engine-core/src/raft_role/learner_state_test.rs b/d-engine-runtime/tests/components/raft_role/learner_state_test.rs similarity index 100% rename from d-engine-core/src/raft_role/learner_state_test.rs rename to d-engine-runtime/tests/components/raft_role/learner_state_test.rs diff --git a/d-engine-core/src/replication/replication_handler_test.rs b/d-engine-runtime/tests/components/replication/replication_handler_test.rs similarity index 100% rename from d-engine-core/src/replication/replication_handler_test.rs rename to d-engine-runtime/tests/components/replication/replication_handler_test.rs diff --git a/d-engine-core/src/raft_test.rs b/d-engine-runtime/tests/raft_test.rs similarity index 100% rename from d-engine-core/src/raft_test.rs rename to d-engine-runtime/tests/raft_test.rs From a2133f419a5e3820fb772962b911b9e4c99a54aa Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Fri, 24 Oct 2025 16:59:47 +0800 Subject: [PATCH 09/26] feat #107: try feature test utils tag again --- Cargo.lock | 366 ++++++++---------- d-engine-client/src/kv.rs | 3 +- d-engine-client/src/kv_test.rs | 6 +- d-engine-client/src/proto/client_ext.rs | 36 +- d-engine-client/src/proto/mod.rs | 122 +----- d-engine-core/src/maybe_clone_oneshot.rs | 10 +- .../default_state_machine_handler_test.rs | 1 - .../src/test_utils/mock/mock_rpc_service.rs | 3 - .../test_utils/mock/mock_storage_engine.rs | 1 - .../test_utils/{ => mock}/mock_type_config.rs | 2 +- d-engine-core/src/test_utils/mock/mod.rs | 7 +- d-engine-core/src/test_utils/mod.rs | 4 +- d-engine-runtime/Cargo.toml | 3 +- .../src/membership/raft_membership_test.rs | 10 +- .../src/network/grpc/grpc_raft_service.rs | 8 +- .../adaptors/file/file_storage_engine.rs | 12 +- .../src/test_utils/integration/mod.rs | 117 ++++-- .../src/test_utils/mock/mock_rpc_services.rs | 140 +++++++ d-engine-runtime/src/test_utils/mock/mod.rs | 3 + d-engine-runtime/src/test_utils/mod.rs | 2 + .../tests/components/election/mod.rs | 1 + d-engine-runtime/tests/components/mod.rs | 4 +- .../tests/components/raft_role/mod.rs | 8 + .../tests/components/replication/mod.rs | 1 + .../replication/replication_handler_test.rs | 28 +- d-engine-runtime/tests/integration_test.rs | 3 +- 26 files changed, 460 insertions(+), 441 deletions(-) rename d-engine-core/src/test_utils/{ => mock}/mock_type_config.rs (96%) create mode 100644 d-engine-runtime/src/test_utils/mock/mock_rpc_services.rs create mode 100644 d-engine-runtime/tests/components/election/mod.rs create mode 100644 d-engine-runtime/tests/components/raft_role/mod.rs create mode 100644 d-engine-runtime/tests/components/replication/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 459ee904..e2989eb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" @@ -46,15 +37,15 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arc-swap" @@ -167,21 +158,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base64" version = "0.22.1" @@ -199,11 +175,11 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.72.0" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.10.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -223,9 +199,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.3" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" @@ -263,10 +239,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.34" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -283,9 +260,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -524,6 +501,7 @@ dependencies = [ "temp-env", "tempfile", "tokio", + "tokio-stream", "tokio-util", "tonic", "tonic-health", @@ -583,12 +561,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -609,6 +587,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + [[package]] name = "fixedbitset" version = "0.5.7" @@ -617,9 +601,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "miniz_oxide", @@ -750,27 +734,21 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.3+wasi-0.2.4", + "wasip2", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" version = "0.3.3" @@ -789,7 +767,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.11.0", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -819,6 +797,12 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + [[package]] name = "heck" version = "0.5.0" @@ -909,9 +893,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "bytes", "futures-channel", @@ -922,7 +906,7 @@ dependencies = [ "hyper", "libc", "pin-project-lite", - "socket2 0.6.0", + "socket2 0.6.1", "tokio", "tower-service", "tracing", @@ -940,23 +924,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.15.5", -] - -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.3", - "cfg-if", - "libc", + "hashbrown 0.16.0", ] [[package]] @@ -989,7 +962,7 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] @@ -1011,18 +984,18 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-link", ] [[package]] @@ -1031,7 +1004,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.10.0", "libc", "redox_syscall 0.5.18", ] @@ -1064,9 +1037,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" @@ -1079,9 +1052,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lru" @@ -1119,15 +1092,15 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] @@ -1161,17 +1134,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -1222,7 +1196,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "libc", @@ -1240,11 +1214,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.50.1" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1262,15 +1236,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -1297,7 +1262,7 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -1329,7 +1294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.11.0", + "indexmap 2.12.0", ] [[package]] @@ -1429,9 +1394,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -1490,9 +1455,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -1561,14 +1526,14 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.10.0", ] [[package]] name = "regex" -version = "1.11.2" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -1578,9 +1543,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1589,9 +1554,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "ring" @@ -1617,12 +1582,6 @@ dependencies = [ "librocksdb-sys", ] -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -1631,22 +1590,22 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.31" +version = "0.23.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" dependencies = [ "log", "once_cell", @@ -1677,9 +1636,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "ring", "rustls-pki-types", @@ -1812,6 +1771,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.11" @@ -1836,12 +1801,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1852,9 +1817,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -1878,15 +1843,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.21.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1959,28 +1924,25 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "pin-project-lite", "signal-hook-registry", - "slab", - "socket2 0.6.0", + "socket2 0.6.1", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -1989,9 +1951,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", @@ -2063,7 +2025,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.11.0", + "indexmap 2.12.0", "serde", "serde_spanned", "toml_datetime", @@ -2279,9 +2241,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" [[package]] name = "untrusted" @@ -2295,7 +2257,7 @@ version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", "wasm-bindgen", ] @@ -2346,10 +2308,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.3+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] @@ -2413,12 +2375,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -2436,20 +2392,20 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.60.2" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets 0.53.3", + "windows-link", ] [[package]] @@ -2470,19 +2426,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -2493,9 +2449,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -2505,9 +2461,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -2517,9 +2473,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -2529,9 +2485,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -2541,9 +2497,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -2553,9 +2509,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -2565,9 +2521,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -2577,9 +2533,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" @@ -2592,9 +2548,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "xattr" @@ -2617,18 +2573,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -2637,15 +2593,15 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/d-engine-client/src/kv.rs b/d-engine-client/src/kv.rs index 2b5d4f88..ed77e9b4 100644 --- a/d-engine-client/src/kv.rs +++ b/d-engine-client/src/kv.rs @@ -13,8 +13,7 @@ use tracing::warn; use super::ClientInner; use crate::ClientApiError; -use crate::client_extensions::ClientResponseExt; -use crate::client_extensions::WriteCommandExt; +use crate::ClientResponseExt; use crate::scoped_timer::ScopedTimer; use d_engine_proto::client::ClientReadRequest; use d_engine_proto::client::ClientResult; diff --git a/d-engine-client/src/kv_test.rs b/d-engine-client/src/kv_test.rs index 8ec0d1c9..a661370c 100644 --- a/d-engine-client/src/kv_test.rs +++ b/d-engine-client/src/kv_test.rs @@ -1,9 +1,8 @@ -use std::sync::Arc; -use std::vec; - use arc_swap::ArcSwap; use bytes::Bytes; use d_engine_proto::common::NodeRole; +use std::sync::Arc; +use std::vec; use tokio::sync::oneshot; use tracing_test::traced_test; @@ -13,7 +12,6 @@ use crate::ClientInner; use crate::ClusterClient; use crate::ConnectionPool; use crate::KvClient; -use crate::client_extensions::ClientResponseExt; use crate::mock_rpc_service::MockNode; use d_engine_proto::client::ClientResponse; use d_engine_proto::client::ClientResult; diff --git a/d-engine-client/src/proto/client_ext.rs b/d-engine-client/src/proto/client_ext.rs index 20a675d4..5da2bc83 100644 --- a/d-engine-client/src/proto/client_ext.rs +++ b/d-engine-client/src/proto/client_ext.rs @@ -1,3 +1,11 @@ +use d_engine_proto::{ + client::{ClientResponse, ClientResult, client_response::SuccessResult}, + error::ErrorCode, +}; +use tracing::error; + +use crate::ClientApiError; + pub trait ClientResponseExt { /// Convert response to boolean write result /// @@ -10,37 +18,13 @@ pub trait ClientResponseExt { /// /// # Returns /// Vector of optional key-value pairs wrapped in Result - fn into_read_results(self) -> std::result::Result>, ClientApiError> { - self.validate_error()?; - match &self.success_result { - Some(SuccessResult::ReadData(data)) => data - .results - .clone() - .into_iter() - .map(|item| { - Ok(Some(ClientResult { - key: item.key, - value: item.value, - })) - }) - .collect(), - _ => { - error!("Invalid response type for read operation"); - unreachable!() - } - } - } + fn into_read_results(self) -> std::result::Result>, ClientApiError>; /// Validate error code in response header /// /// # Internal Logic /// Converts numeric error code to enum variant - fn validate_error(&self) -> std::result::Result<(), ClientApiError> { - match ErrorCode::try_from(self.error).unwrap_or(ErrorCode::Uncategorized) { - ErrorCode::Success => Ok(()), - e => Err(e.into()), - } - } + fn validate_error(&self) -> std::result::Result<(), ClientApiError>; } impl ClientResponseExt for ClientResponse { diff --git a/d-engine-client/src/proto/mod.rs b/d-engine-client/src/proto/mod.rs index d8897868..97e6d4b7 100644 --- a/d-engine-client/src/proto/mod.rs +++ b/d-engine-client/src/proto/mod.rs @@ -1,120 +1,2 @@ -impl ClientResponse { - /// Build success response for write operations - /// - /// # Returns - /// Response with NoError code and write confirmation - pub fn write_success() -> Self { - Self { - error: ErrorCode::Success as i32, - success_result: Some(SuccessResult::WriteAck(true)), - metadata: None, - } - } - - /// Check if the write operation was successful - /// - /// # Returns - /// - `true` if the response indicates a successful write operation - /// - `false` if the response indicates a failed write operation or is not a write response - pub fn is_write_success(&self) -> bool { - self.error == ErrorCode::Success as i32 - && matches!(self.success_result, Some(SuccessResult::WriteAck(true))) - } - - /// Build success response for read operations - /// - /// # Parameters - /// - `results`: Vector of retrieved key-value pairs - pub fn read_results(results: Vec) -> Self { - Self { - error: ErrorCode::Success as i32, - success_result: Some(SuccessResult::ReadData(ReadResults { results })), - metadata: None, - } - } - - /// Build generic error response for any operation type - /// - /// # Parameters - /// - `error_code`: Predefined client request error code - pub fn client_error(error_code: ErrorCode) -> Self { - Self { - error: error_code as i32, - success_result: None, - metadata: None, - } - } - - /// Convert response to boolean write result - /// - /// # Returns - /// - `Ok(true)` on successful write - /// - `Err` with converted error code on failure - pub fn into_write_result(self) -> std::result::Result { - self.validate_error()?; - Ok(match self.success_result { - Some(SuccessResult::WriteAck(success)) => success, - _ => false, - }) - } - - /// Convert response to read results - /// - /// # Returns - /// Vector of optional key-value pairs wrapped in Result - pub fn into_read_results( - self - ) -> std::result::Result>, ClientApiError> { - self.validate_error()?; - match &self.success_result { - Some(SuccessResult::ReadData(data)) => data - .results - .clone() - .into_iter() - .map(|item| { - Ok(Some(ClientResult { - key: item.key, - value: item.value, - })) - }) - .collect(), - _ => { - error!("Invalid response type for read operation"); - unreachable!() - } - } - } - - /// Validate error code in response header - /// - /// # Internal Logic - /// Converts numeric error code to enum variant - pub fn validate_error(&self) -> std::result::Result<(), ClientApiError> { - match ErrorCode::try_from(self.error).unwrap_or(ErrorCode::Uncategorized) { - ErrorCode::Success => Ok(()), - e => Err(e.into()), - } - } - - /// Check if this response indicates the leader's term is outdated - pub fn is_term_outdated(&self) -> bool { - ErrorCode::try_from(self.error).map(|e| e.is_term_outdated()).unwrap_or(false) - } - - /// Check if this response indicates a quorum timeout or failure to receive majority responses - pub fn is_quorum_timeout_or_failure(&self) -> bool { - ErrorCode::try_from(self.error) - .map(|e| e.is_quorum_timeout_or_failure()) - .unwrap_or(false) - } - - /// Check if this response indicates a failure to receive majority responses - pub fn is_propose_failure(&self) -> bool { - ErrorCode::try_from(self.error).map(|e| e.is_propose_failure()).unwrap_or(false) - } - - /// Check if this response indicates a a retry required - pub fn is_retry_required(&self) -> bool { - ErrorCode::try_from(self.error).map(|e| e.is_retry_required()).unwrap_or(false) - } -} +mod client_ext; +pub use client_ext::*; diff --git a/d-engine-core/src/maybe_clone_oneshot.rs b/d-engine-core/src/maybe_clone_oneshot.rs index 0320403b..b2e8b890 100644 --- a/d-engine-core/src/maybe_clone_oneshot.rs +++ b/d-engine-core/src/maybe_clone_oneshot.rs @@ -31,10 +31,9 @@ pub trait RaftOneshot { fn new() -> (Self::Sender, Self::Receiver); } -pub(crate) struct MaybeCloneOneshot; +pub struct MaybeCloneOneshot; -#[allow(dead_code)] -pub(crate) struct MaybeCloneOneshotSender { +pub struct MaybeCloneOneshotSender { inner: oneshot::Sender, #[cfg(test)] @@ -50,8 +49,7 @@ impl Debug for MaybeCloneOneshotSender { } } -#[allow(dead_code)] -pub(crate) struct MaybeCloneOneshotReceiver { +pub struct MaybeCloneOneshotReceiver { inner: oneshot::Receiver, #[cfg(test)] @@ -203,7 +201,7 @@ impl RaftOneshot for MaybeCloneOneshot { } #[derive(Debug)] -pub(crate) struct StreamResponseSender { +pub struct StreamResponseSender { inner: oneshot::Sender, Status>>, #[cfg(test)] diff --git a/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs index e861a252..021dd118 100644 --- a/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs +++ b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs @@ -37,7 +37,6 @@ use crate::MockStateMachine; use crate::SnapshotError; use crate::StateUpdate; use crate::StorageError; -use crate::test_utils::MOCK_STATE_MACHINE_HANDLER_PORT_BASE; use crate::test_utils::MockBuilder; use crate::test_utils::MockTypeConfig; use crate::test_utils::crate_test_snapshot_stream; diff --git a/d-engine-core/src/test_utils/mock/mock_rpc_service.rs b/d-engine-core/src/test_utils/mock/mock_rpc_service.rs index 77337377..23141d17 100644 --- a/d-engine-core/src/test_utils/mock/mock_rpc_service.rs +++ b/d-engine-core/src/test_utils/mock/mock_rpc_service.rs @@ -29,9 +29,6 @@ use d_engine_proto::server::storage::PurgeLogResponse; use d_engine_proto::server::storage::SnapshotResponse; use d_engine_proto::server::storage::snapshot_service_server::SnapshotServiceServer; -// ports for unit tests -pub(crate) const MOCK_STATE_MACHINE_HANDLER_PORT_BASE: u32 = 60400; - pub struct MockNode; impl MockNode { diff --git a/d-engine-core/src/test_utils/mock/mock_storage_engine.rs b/d-engine-core/src/test_utils/mock/mock_storage_engine.rs index 4e5e411c..528bcbae 100644 --- a/d-engine-core/src/test_utils/mock/mock_storage_engine.rs +++ b/d-engine-core/src/test_utils/mock/mock_storage_engine.rs @@ -1,7 +1,6 @@ use std::cell::RefCell; use std::collections::HashMap; use std::sync::Arc; - use uuid::Uuid; use crate::HardState; diff --git a/d-engine-core/src/test_utils/mock_type_config.rs b/d-engine-core/src/test_utils/mock/mock_type_config.rs similarity index 96% rename from d-engine-core/src/test_utils/mock_type_config.rs rename to d-engine-core/src/test_utils/mock/mock_type_config.rs index 23d1070a..ef8eba84 100644 --- a/d-engine-core/src/test_utils/mock_type_config.rs +++ b/d-engine-core/src/test_utils/mock/mock_type_config.rs @@ -1,4 +1,3 @@ -use super::*; use crate::MockCommitHandler; use crate::MockElectionCore; use crate::MockMembership; @@ -10,6 +9,7 @@ use crate::MockStateMachine; use crate::MockStateMachineHandler; use crate::MockTransport; use crate::TypeConfig; +use crate::test_utils::MockStorageEngine; #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd)] pub struct MockTypeConfig; diff --git a/d-engine-core/src/test_utils/mock/mod.rs b/d-engine-core/src/test_utils/mock/mod.rs index 4a20dded..d8c85d28 100644 --- a/d-engine-core/src/test_utils/mock/mod.rs +++ b/d-engine-core/src/test_utils/mock/mod.rs @@ -42,19 +42,18 @@ mod mock_raft_builder; mod mock_rpc; mod mock_rpc_service; mod mock_storage_engine; +mod mock_type_config; pub use mock_raft_builder::*; pub use mock_rpc::*; pub use mock_rpc_service::*; pub use mock_storage_engine::*; +pub use mock_type_config::*; -use super::MockTypeConfig; use super::node_config; -use tokio::sync::watch; - -use crate::Raft; use crate::RaftContext; use d_engine_proto::server::cluster::NodeMeta; +use tokio::sync::watch; pub(crate) fn mock_raft_context( db_path: &str, diff --git a/d-engine-core/src/test_utils/mod.rs b/d-engine-core/src/test_utils/mod.rs index 8a6fd8ba..a56c772f 100644 --- a/d-engine-core/src/test_utils/mod.rs +++ b/d-engine-core/src/test_utils/mod.rs @@ -5,12 +5,10 @@ mod entry_builder; mod mock; mod snapshot; -pub(crate) use snapshot::*; -pub mod mock_type_config; pub use common::*; pub use entry_builder::*; pub use mock::*; -pub use mock_type_config::*; +pub(crate) use snapshot::*; pub fn node_config(db_path: &str) -> crate::RaftNodeConfig { let mut s = diff --git a/d-engine-runtime/Cargo.toml b/d-engine-runtime/Cargo.toml index 3c026534..45173dcc 100644 --- a/d-engine-runtime/Cargo.toml +++ b/d-engine-runtime/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [dependencies] d-engine-proto = { workspace = true } -d-engine-core = { workspace = true, features = ["test-utils"] } +d-engine-core = { workspace = true } tokio = { workspace = true } tonic = { workspace = true } @@ -32,3 +32,4 @@ temp-env = "0.3.6" tracing-test = "0.2" tonic-health = "0.12.3" +tokio-stream = "0.1.16" diff --git a/d-engine-runtime/src/membership/raft_membership_test.rs b/d-engine-runtime/src/membership/raft_membership_test.rs index 71c86696..94b7fbfc 100644 --- a/d-engine-runtime/src/membership/raft_membership_test.rs +++ b/d-engine-runtime/src/membership/raft_membership_test.rs @@ -1,3 +1,4 @@ +use d_engine_core::MockStateMachine; use tokio::sync::oneshot; use tracing_test::traced_test; @@ -7,15 +8,14 @@ use d_engine_core::ConsensusError; use d_engine_core::Error; use crate::RaftTypeConfig; +use crate::test_utils::MockNode; +use crate::test_utils::MockRpcService; +use crate::test_utils::MockStorageEngine; +use crate::test_utils::MockTypeConfig; use d_engine_core::Membership; use d_engine_core::MembershipError; -use d_engine_core::MockStateMachine; use d_engine_core::RaftNodeConfig; use d_engine_core::ensure_safe_join; -use d_engine_core::test_utils::MockNode; -use d_engine_core::test_utils::MockRpcService; -use d_engine_core::test_utils::MockStorageEngine; -use d_engine_core::test_utils::MockTypeConfig; use d_engine_proto::common::AddNode; use d_engine_proto::common::BatchRemove; use d_engine_proto::common::MembershipChange; diff --git a/d-engine-runtime/src/network/grpc/grpc_raft_service.rs b/d-engine-runtime/src/network/grpc/grpc_raft_service.rs index 15189a0d..434bcce2 100644 --- a/d-engine-runtime/src/network/grpc/grpc_raft_service.rs +++ b/d-engine-runtime/src/network/grpc/grpc_raft_service.rs @@ -16,11 +16,11 @@ use tracing::debug; use tracing::error; use tracing::warn; -use crate::MaybeCloneOneshot; use crate::Node; -use crate::RaftEvent; -use crate::RaftOneshot; -use crate::StreamResponseSender; +use d_engine_core::MaybeCloneOneshot; +use d_engine_core::RaftEvent; +use d_engine_core::RaftOneshot; +use d_engine_core::StreamResponseSender; use d_engine_core::TypeConfig; use d_engine_proto::client::ClientReadRequest; use d_engine_proto::client::ClientResponse; diff --git a/d-engine-runtime/src/storage/adaptors/file/file_storage_engine.rs b/d-engine-runtime/src/storage/adaptors/file/file_storage_engine.rs index ae05e8f1..185caf09 100644 --- a/d-engine-runtime/src/storage/adaptors/file/file_storage_engine.rs +++ b/d-engine-runtime/src/storage/adaptors/file/file_storage_engine.rs @@ -19,12 +19,12 @@ use prost::Message; use tonic::async_trait; use tracing::info; -use crate::Error; -use crate::HardState; -use crate::LogStore; -use crate::MetaStore; -use crate::StorageEngine; -use crate::StorageError; +use d_engine_core::Error; +use d_engine_core::HardState; +use d_engine_core::LogStore; +use d_engine_core::MetaStore; +use d_engine_core::StorageEngine; +use d_engine_core::StorageError; use d_engine_proto::common::Entry; use d_engine_proto::common::LogId; diff --git a/d-engine-runtime/src/test_utils/integration/mod.rs b/d-engine-runtime/src/test_utils/integration/mod.rs index 217f6cf5..92b500de 100644 --- a/d-engine-runtime/src/test_utils/integration/mod.rs +++ b/d-engine-runtime/src/test_utils/integration/mod.rs @@ -37,45 +37,44 @@ //! - Cluster formation and interaction tests //! - Failure scenario testing with real component interactions -mod snapshot; -use std::path::PathBuf; -use std::sync::Arc; - -#[allow(unused)] -pub(crate) use snapshot::*; -use tokio::sync::watch; - -use super::generate_insert_commands; use crate::BufferedRaftLog; -use crate::DefaultStateMachineHandler; -use crate::ElectionHandler; use crate::FileStateMachine; use crate::FileStorageEngine; -use crate::FlushPolicy; -use crate::LogSizePolicy; -use crate::MockStateMachine; -use crate::PersistenceConfig; -use crate::PersistenceStrategy; -use crate::RaftLog; use crate::RaftMembership; -use crate::RaftNodeConfig; -use crate::RaftRole; use crate::RaftTypeConfig; -use crate::ReplicationHandler; -use crate::StateMachine; -use crate::StorageEngine; -use crate::TypeConfig; -use crate::alias::MOF; -use crate::alias::ROF; -use crate::alias::SMOF; -use crate::alias::TROF; use crate::grpc::grpc_transport::GrpcTransport; -use crate::test_utils::MockTypeConfig; use crate::test_utils::mock_state_machine; +use bytes::Bytes; +use bytes::BytesMut; +use d_engine_core::DefaultStateMachineHandler; +use d_engine_core::ElectionHandler; +use d_engine_core::FlushPolicy; +use d_engine_core::LogSizePolicy; +use d_engine_core::MockStateMachine; +use d_engine_core::PersistenceConfig; +use d_engine_core::PersistenceStrategy; +use d_engine_core::RaftLog; +use d_engine_core::RaftNodeConfig; +use d_engine_core::RaftRole; +use d_engine_core::ReplicationHandler; +use d_engine_core::StateMachine; +use d_engine_core::StorageEngine; +use d_engine_core::TypeConfig; +use d_engine_core::alias::MOF; +use d_engine_core::alias::ROF; +use d_engine_core::alias::SMOF; +use d_engine_core::alias::TROF; +use d_engine_core::convert::safe_kv_bytes; +use d_engine_core::test_utils::MockTypeConfig; +use d_engine_proto::client::WriteCommand; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; use d_engine_proto::common::NodeStatus; use d_engine_proto::server::cluster::NodeMeta; +use std::ops::RangeInclusive; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::sync::watch; #[allow(dead_code)] pub struct TestContext @@ -253,3 +252,65 @@ pub fn node_config(db_path: &str) -> RaftNodeConfig { s.cluster.db_root_dir = PathBuf::from(db_path); s } + +pub fn generate_insert_commands(ids: Vec) -> Bytes { + let mut buffer = BytesMut::new(); + + for id in ids { + let cmd = WriteCommand::insert(safe_kv_bytes(id), safe_kv_bytes(id)); + cmd.encode(&mut buffer).expect("Failed to encode insert command"); + } + + buffer.freeze() +} + +pub fn generate_delete_commands(range: RangeInclusive) -> Bytes { + let mut buffer = BytesMut::new(); + + for id in range { + let cmd = WriteCommand::delete(safe_kv_bytes(id)); + cmd.encode(&mut buffer).expect("Failed to encode delete command"); + } + + buffer.freeze() +} + +///Dependes on external id to specify the local log entry index. +/// If duplicated ids are specified, then the only one entry will be inserted. +pub async fn simulate_insert_command( + raft_log: &Arc>>, + ids: Vec, + term: u64, +) { + let mut entries = Vec::new(); + for id in ids { + let log = Entry { + index: raft_log.pre_allocate_raft_logs_next_index(), + term, + payload: Some(EntryPayload::command(generate_insert_commands(vec![id]))), + }; + entries.push(log); + } + raft_log.insert_batch(entries).await.unwrap(); + raft_log.flush().await.unwrap(); +} + +#[allow(dead_code)] +pub async fn simulate_delete_command( + raft_log: &Arc>>, + id_range: RangeInclusive, + term: u64, +) { + let mut entries = Vec::new(); + for id in id_range { + let log = Entry { + index: raft_log.pre_allocate_raft_logs_next_index(), + term, + payload: Some(EntryPayload::command(generate_delete_commands(id..=id))), + }; + entries.push(log); + } + if let Err(e) = raft_log.insert_batch(entries).await { + panic!("error: {e:?}"); + } +} diff --git a/d-engine-runtime/src/test_utils/mock/mock_rpc_services.rs b/d-engine-runtime/src/test_utils/mock/mock_rpc_services.rs new file mode 100644 index 00000000..e164974b --- /dev/null +++ b/d-engine-runtime/src/test_utils/mock/mock_rpc_services.rs @@ -0,0 +1,140 @@ +use d_engine_core::StorageError; +use d_engine_proto::client::ClientResponse; +use d_engine_proto::client::raft_client_service_server::RaftClientServiceServer; +use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementServiceServer; +use d_engine_proto::server::cluster::{ + ClusterConfUpdateResponse, ClusterMembership, JoinResponse, LeaderDiscoveryResponse, +}; +use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::election::raft_election_service_server::RaftElectionServiceServer; +use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationServiceServer; +use d_engine_proto::server::storage::snapshot_service_server::SnapshotServiceServer; +use d_engine_proto::server::storage::{PurgeLogResponse, SnapshotChunk, SnapshotResponse}; +use std::net::SocketAddr; +use std::sync::Arc; +use tokio::net::TcpListener; +use tokio::sync::oneshot; +use tonic::codec::CompressionEncoding; +use tonic::transport::Channel; +use tonic_health::server::health_reporter; +use tracing::{debug, info}; + +#[derive(Clone, Default)] +pub struct MockRpcService { + pub server_port: Option, + // Expected responses for each method + pub expected_vote_response: Option>, + pub expected_append_entries_response: Option>, + pub expected_update_cluster_conf_response: + Option>, + pub expected_client_propose_response: Option>, + pub expected_client_read_response: Option>, + + #[allow(clippy::type_complexity)] + pub expected_metadata_response: + Option Result + Send + Sync>>, + + pub expected_snapshot_response: Option>, + pub expected_stream_snapshot_response: Option>, + pub expected_purge_log_response: Option>, + pub expected_join_cluster_response: Option>, + pub expected_discover_leader_response: Option>, +} + +pub struct MockNode; + +impl MockNode { + pub async fn mock_listener( + mut mock_service: MockRpcService, + rx: oneshot::Receiver<()>, + is_ready: bool, + ) -> Result<(u16, SocketAddr)> { + // Return port + address + let (mut health_reporter, health_service) = health_reporter(); + if is_ready { + health_reporter.set_serving::>().await; + health_reporter.set_serving::>().await; + health_reporter.set_serving::>().await; + health_reporter.set_serving::>().await; + health_reporter.set_serving::>().await; + info!("set service is serving"); + } else { + health_reporter.set_not_serving::>().await; + health_reporter.set_not_serving::>().await; + health_reporter + .set_not_serving::>() + .await; + health_reporter + .set_not_serving::>() + .await; + health_reporter.set_not_serving::>().await; + info!("set service is not serving"); + } + + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().map_err(StorageError::IoError)?; + let port = addr.port(); + debug!("starting mock rpc service:port={port}",); + + // Set the port in the service + mock_service.set_port(port); + + let mock_service = Arc::new(mock_service); + + let _r = tokio::spawn(async move { + tonic::transport::Server::builder() + .add_service(health_service) + .add_service( + RaftClientServiceServer::from_arc(mock_service.clone()) + .accept_compressed(CompressionEncoding::Gzip) + .send_compressed(CompressionEncoding::Gzip), + ) + .add_service( + RaftElectionServiceServer::from_arc(mock_service.clone()) + .accept_compressed(CompressionEncoding::Gzip) + .send_compressed(CompressionEncoding::Gzip), + ) + .add_service( + RaftReplicationServiceServer::from_arc(mock_service.clone()) + .accept_compressed(CompressionEncoding::Gzip) + .send_compressed(CompressionEncoding::Gzip), + ) + .add_service( + ClusterManagementServiceServer::from_arc(mock_service.clone()) + .accept_compressed(CompressionEncoding::Gzip) + .send_compressed(CompressionEncoding::Gzip), + ) + .add_service( + SnapshotServiceServer::from_arc(mock_service) + .accept_compressed(CompressionEncoding::Gzip) + .send_compressed(CompressionEncoding::Gzip), + ) + // add as a dev-dependency the crate `tokio-stream` with feature `net` enabled + .serve_with_incoming_shutdown( + tokio_stream::wrappers::TcpListenerStream::new(listener), + async { + rx.await.ok(); + }, + ) + // .serve_with_shutdown("127.0.0.1:50051".parse().unwrap(), ) + .await + .unwrap(); + }); + + Ok((port, addr)) // Return both port and address + } + + // Update helper functions to handle dynamic ports + pub(crate) async fn mock_channel_with_port(port: u16) -> Channel { + Channel::from_shared(format!("http://127.0.0.1:{port}")) + .expect("valid address") + .connect() + .await + .expect("connection failed") + } + + pub(crate) fn tcp_addr_to_http_addr(addr: String) -> String { + format!("http://{addr}") + } +} diff --git a/d-engine-runtime/src/test_utils/mock/mod.rs b/d-engine-runtime/src/test_utils/mock/mod.rs index 3eee3bd4..20eacdb1 100644 --- a/d-engine-runtime/src/test_utils/mock/mod.rs +++ b/d-engine-runtime/src/test_utils/mock/mod.rs @@ -1,5 +1,8 @@ mod mock_node_builder; +mod mock_rpc_services; + pub use mock_node_builder::*; +pub use mock_rpc_services::*; use crate::Node; use d_engine_core::{ diff --git a/d-engine-runtime/src/test_utils/mod.rs b/d-engine-runtime/src/test_utils/mod.rs index 07604006..e7b56a44 100644 --- a/d-engine-runtime/src/test_utils/mod.rs +++ b/d-engine-runtime/src/test_utils/mod.rs @@ -1,3 +1,5 @@ +mod integration; mod mock; +pub use integration::*; pub use mock::*; diff --git a/d-engine-runtime/tests/components/election/mod.rs b/d-engine-runtime/tests/components/election/mod.rs new file mode 100644 index 00000000..6a01979b --- /dev/null +++ b/d-engine-runtime/tests/components/election/mod.rs @@ -0,0 +1 @@ +mod election_handler_test; diff --git a/d-engine-runtime/tests/components/mod.rs b/d-engine-runtime/tests/components/mod.rs index e05c55e1..f27ea6b4 100644 --- a/d-engine-runtime/tests/components/mod.rs +++ b/d-engine-runtime/tests/components/mod.rs @@ -1,2 +1,4 @@ mod buffered_raft_log_test; -mod raft_log_test; +mod election; +mod raft_role; +mod replication; diff --git a/d-engine-runtime/tests/components/raft_role/mod.rs b/d-engine-runtime/tests/components/raft_role/mod.rs new file mode 100644 index 00000000..7e8fe882 --- /dev/null +++ b/d-engine-runtime/tests/components/raft_role/mod.rs @@ -0,0 +1,8 @@ +#[cfg(test)] +mod candidate_state_test; +#[cfg(test)] +mod follower_state_test; +#[cfg(test)] +mod leader_state_test; +#[cfg(test)] +mod learner_state_test; diff --git a/d-engine-runtime/tests/components/replication/mod.rs b/d-engine-runtime/tests/components/replication/mod.rs new file mode 100644 index 00000000..13c10a49 --- /dev/null +++ b/d-engine-runtime/tests/components/replication/mod.rs @@ -0,0 +1 @@ +mod replication_handler_test; diff --git a/d-engine-runtime/tests/components/replication/replication_handler_test.rs b/d-engine-runtime/tests/components/replication/replication_handler_test.rs index db65afc5..07498c43 100644 --- a/d-engine-runtime/tests/components/replication/replication_handler_test.rs +++ b/d-engine-runtime/tests/components/replication/replication_handler_test.rs @@ -18,7 +18,6 @@ use crate::Error; use crate::LeaderStateSnapshot; use crate::MockMembership; use crate::MockRaftLog; -use crate::MockStateMachine; use crate::MockTransport; use crate::NetworkError; use crate::PeerUpdate; @@ -77,8 +76,7 @@ async fn test_retrieve_to_be_synced_logs_for_peers_case1() { let max_entries = 100; let peer_next_indices = HashMap::from([(peer3_id, leader_last_index_before_inserting_new_entries)]); - let handler = - ReplicationHandler::>::new(my_id); + let handler = ReplicationHandler::::new(my_id); let r = handler.retrieve_to_be_synced_logs_for_peers( new_entries.clone(), @@ -126,8 +124,7 @@ async fn test_retrieve_to_be_synced_logs_for_peers_case2() { let max_entries = 100; let peer_next_indices = HashMap::from([(peer3_id, leader_last_index_before_inserting_new_entries)]); - let handler = - ReplicationHandler::>::new(my_id); + let handler = ReplicationHandler::::new(my_id); let r = handler.retrieve_to_be_synced_logs_for_peers( new_entries.clone(), @@ -172,8 +169,7 @@ async fn test_retrieve_to_be_synced_logs_for_peers_case3() { let max_entries = 100; let peer_next_indices = HashMap::from([(peer3_id, leader_last_index_before_inserting_new_entries)]); - let handler = - ReplicationHandler::>::new(my_id); + let handler = ReplicationHandler::::new(my_id); let r = handler.retrieve_to_be_synced_logs_for_peers( new_entries.clone(), @@ -226,8 +222,7 @@ async fn test_retrieve_to_be_synced_logs_for_peers_case4_1() { payload: Some(EntryPayload::command(generate_insert_commands(vec![1]))), }]; let peer_next_indices = HashMap::from([(peer3_id, peer3_next_id)]); - let handler = - ReplicationHandler::>::new(my_id); + let handler = ReplicationHandler::::new(my_id); let r = handler.retrieve_to_be_synced_logs_for_peers( new_entries.clone(), @@ -283,8 +278,7 @@ async fn test_retrieve_to_be_synced_logs_for_peers_case4_2() { payload: Some(EntryPayload::command(generate_insert_commands(vec![1]))), }]; let peer_next_indices = HashMap::from([(peer3_id, peer3_next_id)]); - let handler = - ReplicationHandler::>::new(my_id); + let handler = ReplicationHandler::::new(my_id); let r = handler.retrieve_to_be_synced_logs_for_peers( new_entries.clone(), @@ -322,8 +316,7 @@ async fn test_retrieve_to_be_synced_logs_for_peers_case5() { (my_id, 1), (peer3_id, leader_last_index_before_inserting_new_entries), ]); - let handler = - ReplicationHandler::>::new(my_id); + let handler = ReplicationHandler::::new(my_id); let r = handler.retrieve_to_be_synced_logs_for_peers( new_entries.clone(), @@ -348,8 +341,7 @@ async fn test_retrieve_to_be_synced_logs_for_peers_case5() { async fn test_generate_new_entries_case1() { let context = setup_raft_components("/tmp/test_generate_new_entries_case1", None, false); let my_id = 1; - let handler = - ReplicationHandler::>::new(my_id); + let handler = ReplicationHandler::::new(my_id); let last_id = context.raft_log.last_entry_id(); let commands = vec![]; let current_term = 1; @@ -368,8 +360,7 @@ async fn test_generate_new_entries_case1() { async fn test_generate_new_entries_case2() { let context = setup_raft_components("/tmp/test_generate_new_entries_case2", None, false); let my_id = 1; - let handler = - ReplicationHandler::>::new(my_id); + let handler = ReplicationHandler::::new(my_id); let last_id = context.raft_log.last_entry_id(); debug!("last_id: {}", last_id); let commands = vec![WriteCommand::delete(safe_kv_bytes(1))]; @@ -406,8 +397,7 @@ async fn test_build_append_request_case() { let peer2_next_index = 3; let peer3_id = 3; let peer3_next_index = 1; - let handler = - ReplicationHandler::>::new(my_id); + let handler = ReplicationHandler::::new(my_id); // Prepare entries to be replicated for each peer let entries_per_peer: DashMap> = DashMap::new(); entries_per_peer.insert( diff --git a/d-engine-runtime/tests/integration_test.rs b/d-engine-runtime/tests/integration_test.rs index 2758dbf6..38286a4b 100644 --- a/d-engine-runtime/tests/integration_test.rs +++ b/d-engine-runtime/tests/integration_test.rs @@ -1 +1,2 @@ -mod integration_test; +mod components; +mod raft_test; From 493fd6bf99139c6d6d0162111c602ad21c9cf150 Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Wed, 29 Oct 2025 16:50:11 +0800 Subject: [PATCH 10/26] feat #167: fix all compile errors --- Cargo.lock | 67 ++- d-engine-core/Cargo.toml | 10 +- .../commit_handler/default_commit_handler.rs | 2 +- .../default_commit_handler_test.rs | 2 +- d-engine-core/src/commit_handler/mod.rs | 6 +- d-engine-core/src/election/mod.rs | 5 +- d-engine-core/src/event.rs | 14 +- d-engine-core/src/lib.rs | 26 +- d-engine-core/src/maybe_clone_oneshot.rs | 36 +- d-engine-core/src/membership.rs | 4 +- .../mock => }/mock_storage_engine.rs | 0 .../{test_utils/mock => }/mock_type_config.rs | 2 +- .../backgroup_snapshot_transfer_test.rs | 2 +- d-engine-core/src/network/mod.rs | 10 +- d-engine-core/src/purge/default_executor.rs | 2 +- .../src/purge/default_executor_test.rs | 2 +- d-engine-core/src/purge/mod.rs | 6 +- d-engine-core/src/raft.rs | 52 +-- d-engine-core/src/raft_context.rs | 38 +- .../src/raft_role/candidate_state.rs | 8 +- d-engine-core/src/raft_role/follower_state.rs | 4 +- d-engine-core/src/raft_role/leader_state.rs | 56 +-- d-engine-core/src/raft_role/learner_state.rs | 4 +- d-engine-core/src/raft_role/mod.rs | 24 +- d-engine-core/src/raft_role/role_state.rs | 2 +- d-engine-core/src/replication/mod.rs | 4 +- .../src/replication/replication_handler.rs | 18 +- .../default_state_machine_handler_test.rs | 2 +- .../src/state_machine_handler/mod.rs | 7 +- .../snapshot_policy/log_size.rs | 2 +- .../snapshot_policy/mod.rs | 6 +- d-engine-core/src/storage/raft_log.rs | 5 +- d-engine-core/src/storage/state_machine.rs | 7 +- d-engine-core/src/storage/storage_engine.rs | 6 +- .../src/test_utils/mock/mock_raft_builder.rs | 4 +- d-engine-core/src/test_utils/mock/mod.rs | 8 +- d-engine-core/src/test_utils/mod.rs | 2 +- d-engine-core/src/test_utils/snapshot.rs | 4 +- d-engine-core/src/utils/stream.rs | 4 +- d-engine-runtime/Cargo.toml | 29 +- d-engine-runtime/src/lib.rs | 4 +- .../src/membership/raft_membership_test.rs | 8 +- .../src/network/connection_cache.rs | 4 +- .../src/network/connection_cache_test.rs | 6 +- .../src/network/grpc/grpc_raft_service.rs | 12 +- .../network/grpc/grpc_raft_service_test.rs | 16 +- .../src/network/grpc/grpc_transport.rs | 34 +- .../src/network/grpc/grpc_transport_test.rs | 18 +- d-engine-runtime/src/network/grpc/mod.rs | 32 +- .../src/network/health_checker.rs | 8 +- .../src/network/health_checker_test.rs | 4 +- d-engine-runtime/src/node/builder.rs | 54 +-- d-engine-runtime/src/node/builder_test.rs | 30 +- d-engine-runtime/src/node/node_test.rs | 24 +- .../src/node/type_config/raft_type_config.rs | 16 +- .../storage/adaptors/file/file_engine_test.rs | 12 +- .../adaptors/file/file_state_machine.rs | 8 +- .../adaptors/file/file_state_machine_test.rs | 2 +- .../adaptors/file/file_storage_engine.rs | 5 +- .../adaptors/file/file_storage_engine_test.rs | 11 +- .../adaptors/rocksdb/rocksdb_engine_test.rs | 20 +- .../adaptors/rocksdb/rocksdb_state_machine.rs | 8 +- .../rocksdb/rocksdb_storage_engine.rs | 12 +- .../src/storage/buffered/buffered_raft_log.rs | 50 +-- .../buffered/buffered_raft_log_test.rs | 74 ++-- d-engine-runtime/src/storage/mod.rs | 2 +- .../src/test_utils/integration/mod.rs | 6 +- .../src/test_utils/mock/mock_node_builder.rs | 52 +-- .../src/test_utils/mock/mock_rpc_services.rs | 419 +++++++++++++++++- d-engine-runtime/src/test_utils/mock/mod.rs | 11 +- d-engine-runtime/src/test_utils/mod.rs | 2 + d-engine-runtime/src/test_utils/snapshot.rs | 237 ++++++++++ d-engine-runtime/src/utils/async_task.rs | 2 +- d-engine-runtime/src/utils/async_task_test.rs | 16 +- d-engine-runtime/src/utils/cluster.rs | 8 +- d-engine-runtime/src/utils/mod.rs | 12 - d-engine-runtime/src/utils/utils_test.rs | 8 +- .../components/buffered_raft_log_test.rs | 70 ++- .../election/election_handler_test.rs | 30 +- .../tests/components/election/mod.rs | 1 + d-engine-runtime/tests/components/mod.rs | 1 + .../raft_role/candidate_state_test.rs | 69 ++- .../raft_role/follower_state_test.rs | 90 ++-- .../components/raft_role/leader_state_test.rs | 121 +++-- .../raft_role/learner_state_test.rs | 45 +- .../tests/{ => components}/raft_test.rs | 48 +- .../replication/replication_handler_test.rs | 84 ++-- d-engine-runtime/tests/integration_test.rs | 2 +- 88 files changed, 1477 insertions(+), 823 deletions(-) rename d-engine-core/src/{test_utils/mock => }/mock_storage_engine.rs (100%) rename d-engine-core/src/{test_utils/mock => }/mock_type_config.rs (95%) create mode 100644 d-engine-runtime/src/test_utils/snapshot.rs rename d-engine-runtime/tests/{ => components}/raft_test.rs (97%) diff --git a/Cargo.lock b/Cargo.lock index e2989eb4..c55fe2e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -239,9 +239,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.41" +version = "1.2.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" dependencies = [ "find-msvc-tools", "jobserver", @@ -483,16 +483,27 @@ dependencies = [ ] [[package]] -name = "d-engine-server" +name = "d-engine-runtime" version = "0.2.0" dependencies = [ "arc-swap", + "async-compression", + "bincode", "bytes", "config", + "crc32fast", + "crossbeam", + "crossbeam-skiplist", "d-engine-core", "d-engine-proto", "dashmap", "futures", + "http-body", + "http-body-util", + "metrics", + "mockall", + "nanoid", + "parking_lot", "prost", "rcgen", "rocksdb", @@ -502,11 +513,13 @@ dependencies = [ "tempfile", "tokio", "tokio-stream", + "tokio-tar", "tokio-util", "tonic", "tonic-health", "tracing", "tracing-test", + "uuid", ] [[package]] @@ -524,9 +537,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", ] @@ -601,9 +614,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -968,9 +981,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1627,9 +1640,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "zeroize", ] @@ -2318,9 +2331,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -2329,25 +2342,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2355,22 +2354,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] diff --git a/d-engine-core/Cargo.toml b/d-engine-core/Cargo.toml index 319e1277..b9d826fc 100644 --- a/d-engine-core/Cargo.toml +++ b/d-engine-core/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" edition = "2024" +[features] +test-utils = ["mockall","uuid", "tonic-health"] + [dependencies] d-engine-proto = { workspace = true } @@ -35,18 +38,17 @@ http-body = "1.0" http-body-util = "0.1.3" crossbeam-skiplist = "0.1" crossbeam = "0.8" - +mockall = { version = "0.12.1", optional = true } +tonic-health = { version = "0.12.3", optional = true } # Compress/decompress data stream async-compression = { version = "0.4", features = ["tokio", "gzip"] } async-stream = "0.3.6" +uuid = { version = "1", features = ["v4"], optional = true } [dev-dependencies] -mockall = "0.12.1" tracing-test = "0.2" serial_test = "3.2.0" temp-env = "0.3.6" tokio = { version = "1", features = ["test-util", "process"] } parking_lot = "0.12.3" -uuid = { version = "1", features = ["v4"] } -tonic-health = "0.12.3" nix = { version = "0.30.1", features = ["fs"] } diff --git a/d-engine-core/src/commit_handler/default_commit_handler.rs b/d-engine-core/src/commit_handler/default_commit_handler.rs index 909e07ac..b21d5927 100644 --- a/d-engine-core/src/commit_handler/default_commit_handler.rs +++ b/d-engine-core/src/commit_handler/default_commit_handler.rs @@ -114,7 +114,7 @@ impl DefaultCommitHandler where T: TypeConfig, { - pub(crate) fn new( + pub fn new( my_id: u32, my_role: i32, my_current_term: u64, diff --git a/d-engine-core/src/commit_handler/default_commit_handler_test.rs b/d-engine-core/src/commit_handler/default_commit_handler_test.rs index 9bf15702..a0dbaf10 100644 --- a/d-engine-core/src/commit_handler/default_commit_handler_test.rs +++ b/d-engine-core/src/commit_handler/default_commit_handler_test.rs @@ -17,12 +17,12 @@ use crate::Error; use crate::MockMembership; use crate::MockRaftLog; use crate::MockStateMachineHandler; +use crate::MockTypeConfig; use crate::NewCommitData; use crate::RaftConfig; use crate::RaftEvent; use crate::RaftNodeConfig; use crate::Result; -use crate::test_utils::MockTypeConfig; use crate::test_utils::generate_insert_commands; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; diff --git a/d-engine-core/src/commit_handler/mod.rs b/d-engine-core/src/commit_handler/mod.rs index d2a91098..b0b9b677 100644 --- a/d-engine-core/src/commit_handler/mod.rs +++ b/d-engine-core/src/commit_handler/mod.rs @@ -55,15 +55,13 @@ pub use default_commit_handler::*; #[cfg(test)] mod default_commit_handler_test; -//-------------------------------- -// CommitHandler trait definition -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use mockall::automock; use tonic::async_trait; use crate::Result; -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-utils"), automock)] #[async_trait] pub trait CommitHandler: Send + Sync + 'static { async fn run(&mut self) -> Result<()>; diff --git a/d-engine-core/src/election/mod.rs b/d-engine-core/src/election/mod.rs index 8f8e502b..02c01611 100644 --- a/d-engine-core/src/election/mod.rs +++ b/d-engine-core/src/election/mod.rs @@ -14,7 +14,8 @@ use std::sync::Arc; ///-------------------------------------- /// Trait Definition -#[cfg(test)] + +#[cfg(any(test, feature = "test-utils"))] use mockall::automock; use tonic::async_trait; @@ -36,7 +37,7 @@ pub struct StateUpdate { pub new_voted_for: Option, } -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-utils"), automock)] #[async_trait] pub trait ElectionCore: Send + Sync + 'static where diff --git a/d-engine-core/src/event.rs b/d-engine-core/src/event.rs index 83881d9a..0dabb569 100644 --- a/d-engine-core/src/event.rs +++ b/d-engine-core/src/event.rs @@ -29,10 +29,10 @@ use d_engine_proto::server::storage::SnapshotMetadata; use d_engine_proto::server::storage::SnapshotResponse; #[derive(Debug, Clone)] -pub(crate) struct NewCommitData { - pub(crate) new_commit_index: u64, - pub(crate) role: i32, - pub(crate) current_term: u64, +pub struct NewCommitData { + pub new_commit_index: u64, + pub role: i32, + pub current_term: u64, } #[derive(Debug)] @@ -120,8 +120,8 @@ pub enum RaftEvent { PromoteReadyLearners, } -#[cfg(test)] -#[cfg_attr(test, derive(Debug, Clone))] +#[cfg(any(test, feature = "test-utils"))] +#[cfg_attr(any(test, feature = "test-utils"), derive(Debug, Clone))] #[allow(unused)] pub enum TestEvent { ReceiveVoteRequest(VoteRequest), @@ -158,7 +158,7 @@ pub enum TestEvent { PromoteReadyLearners, } -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] pub fn raft_event_to_test_event(event: &RaftEvent) -> TestEvent { match event { RaftEvent::ReceiveVoteRequest(req, _) => TestEvent::ReceiveVoteRequest(*req), diff --git a/d-engine-core/src/lib.rs b/d-engine-core/src/lib.rs index 1d7ab21d..411a8226 100644 --- a/d-engine-core/src/lib.rs +++ b/d-engine-core/src/lib.rs @@ -25,17 +25,17 @@ pub use network::*; pub use raft::*; pub use storage::*; +pub use commit_handler::*; pub use election::*; +pub use maybe_clone_oneshot::*; +pub use purge::*; +pub use raft_context::*; pub use replication::*; pub use state_machine_handler::*; -pub(crate) use commit_handler::*; -pub(crate) use maybe_clone_oneshot::*; -pub(crate) use purge::*; -pub(crate) use raft_context::*; - #[doc(hidden)] pub use raft_role::*; + #[doc(hidden)] pub use utils::*; @@ -46,9 +46,23 @@ pub use type_config::*; #[cfg(test)] mod raft_oneshot_test; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] pub mod test_utils; +#[cfg(any(test, feature = "test-utils"))] +pub use test_utils::*; + +#[cfg(any(test, feature = "test-utils"))] +mod mock_storage_engine; +#[cfg(any(test, feature = "test-utils"))] +mod mock_type_config; + +#[cfg(any(test, feature = "test-utils"))] +pub use mock_storage_engine::*; + +#[cfg(any(test, feature = "test-utils"))] +pub use mock_type_config::*; + /// In raft, during any Leader to Peer communication, /// if received response term is bigger than Leader's, /// the current Leader need downgrade to follower diff --git a/d-engine-core/src/maybe_clone_oneshot.rs b/d-engine-core/src/maybe_clone_oneshot.rs index b2e8b890..21e898b8 100644 --- a/d-engine-core/src/maybe_clone_oneshot.rs +++ b/d-engine-core/src/maybe_clone_oneshot.rs @@ -17,7 +17,7 @@ use std::pin::Pin; use std::task::Context; use std::task::Poll; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use tokio::sync::broadcast; use tokio::sync::oneshot; use tonic::Status; @@ -36,7 +36,7 @@ pub struct MaybeCloneOneshot; pub struct MaybeCloneOneshotSender { inner: oneshot::Sender, - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] test_inner: Option>, // None for non-cloneable types } @@ -52,10 +52,10 @@ impl Debug for MaybeCloneOneshotSender { pub struct MaybeCloneOneshotReceiver { inner: oneshot::Receiver, - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] test_inner: Option>, // None for non-cloneable types } -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] impl MaybeCloneOneshotSender { pub fn send( &self, @@ -70,7 +70,7 @@ impl MaybeCloneOneshotSender { } } -#[cfg(not(test))] +#[cfg(not(any(test, feature = "test-utils")))] impl MaybeCloneOneshotSender { pub fn send( self, @@ -81,7 +81,7 @@ impl MaybeCloneOneshotSender { } impl MaybeCloneOneshotReceiver { - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] pub async fn recv(&mut self) -> Result { if let Some(rx) = &mut self.test_inner { rx.recv().await @@ -92,7 +92,7 @@ impl MaybeCloneOneshotReceiver { } } -#[cfg(not(test))] +#[cfg(not(any(test, feature = "test-utils")))] impl Future for MaybeCloneOneshotReceiver { type Output = Result; @@ -103,7 +103,7 @@ impl Future for MaybeCloneOneshotReceiver { unsafe { self.map_unchecked_mut(|s| &mut s.inner) }.poll(cx) } } -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] impl Future for MaybeCloneOneshotReceiver { type Output = Result; @@ -136,7 +136,7 @@ impl Future for MaybeCloneOneshotReceiver { } } -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] impl Clone for MaybeCloneOneshotSender { fn clone(&self) -> Self { let (sender, _) = oneshot::channel(); @@ -146,7 +146,7 @@ impl Clone for MaybeCloneOneshotSender { } } } -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] impl Clone for MaybeCloneOneshotReceiver { fn clone(&self) -> Self { let (_, receiver) = oneshot::channel(); @@ -157,7 +157,7 @@ impl Clone for MaybeCloneOneshotReceiver { } } } -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] impl RaftOneshot for MaybeCloneOneshot { type Sender = MaybeCloneOneshotSender; type Receiver = MaybeCloneOneshotReceiver; @@ -178,7 +178,7 @@ impl RaftOneshot for MaybeCloneOneshot { } } -#[cfg(not(test))] +#[cfg(not(any(test, feature = "test-utils")))] impl RaftOneshot for MaybeCloneOneshot { type Sender = MaybeCloneOneshotSender; type Receiver = MaybeCloneOneshotReceiver; @@ -188,12 +188,12 @@ impl RaftOneshot for MaybeCloneOneshot { ( MaybeCloneOneshotSender { inner: tx, - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] test_inner: None, }, MaybeCloneOneshotReceiver { inner: rx, - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] test_inner: None, }, ) @@ -204,7 +204,7 @@ impl RaftOneshot for MaybeCloneOneshot { pub struct StreamResponseSender { inner: oneshot::Sender, Status>>, - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] test_inner: Option, Status>>>, } @@ -218,7 +218,7 @@ impl StreamResponseSender { ( Self { inner: inner_tx, - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] test_inner: None, }, inner_rx, @@ -229,10 +229,10 @@ impl StreamResponseSender { self, value: std::result::Result, Status>, ) -> Result<(), Box, Status>>> { - #[cfg(not(test))] + #[cfg(not(any(test, feature = "test-utils")))] return self.inner.send(value).map_err(Box::new); - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] if let Some(tx) = self.test_inner { tx.send(value).map(|_| ()).map_err(|e| Box::new(e.0)) } else { diff --git a/d-engine-core/src/membership.rs b/d-engine-core/src/membership.rs index f7e54f45..deefde01 100644 --- a/d-engine-core/src/membership.rs +++ b/d-engine-core/src/membership.rs @@ -1,4 +1,4 @@ -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use mockall::automock; use tonic::async_trait; use tonic::transport::Channel; @@ -31,7 +31,7 @@ impl ConnectionType { } } -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-utils"), automock)] #[async_trait] pub trait Membership: Sync + Send + 'static where diff --git a/d-engine-core/src/test_utils/mock/mock_storage_engine.rs b/d-engine-core/src/mock_storage_engine.rs similarity index 100% rename from d-engine-core/src/test_utils/mock/mock_storage_engine.rs rename to d-engine-core/src/mock_storage_engine.rs diff --git a/d-engine-core/src/test_utils/mock/mock_type_config.rs b/d-engine-core/src/mock_type_config.rs similarity index 95% rename from d-engine-core/src/test_utils/mock/mock_type_config.rs rename to d-engine-core/src/mock_type_config.rs index ef8eba84..7be44664 100644 --- a/d-engine-core/src/test_utils/mock/mock_type_config.rs +++ b/d-engine-core/src/mock_type_config.rs @@ -9,7 +9,7 @@ use crate::MockStateMachine; use crate::MockStateMachineHandler; use crate::MockTransport; use crate::TypeConfig; -use crate::test_utils::MockStorageEngine; +use crate::mock_storage_engine::MockStorageEngine; #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd)] pub struct MockTypeConfig; diff --git a/d-engine-core/src/network/backgroup_snapshot_transfer_test.rs b/d-engine-core/src/network/backgroup_snapshot_transfer_test.rs index d392e190..74b6c67b 100644 --- a/d-engine-core/src/network/backgroup_snapshot_transfer_test.rs +++ b/d-engine-core/src/network/backgroup_snapshot_transfer_test.rs @@ -10,12 +10,12 @@ use tracing::debug; use super::*; use crate::Error; +use crate::MockTypeConfig; use crate::NetworkError; use crate::SnapshotConfig; use crate::SnapshotError; use crate::StorageError; use crate::test_utils::MockNode; -use crate::test_utils::MockTypeConfig; use crate::test_utils::create_snapshot_stream; use d_engine_proto::server::storage::SnapshotChunk; use d_engine_proto::server::storage::SnapshotResponse; diff --git a/d-engine-core/src/network/mod.rs b/d-engine-core/src/network/mod.rs index 94f67916..372668c4 100644 --- a/d-engine-core/src/network/mod.rs +++ b/d-engine-core/src/network/mod.rs @@ -12,7 +12,7 @@ mod backgroup_snapshot_transfer; mod backgroup_snapshot_transfer_test; pub(crate) use backgroup_snapshot_transfer::*; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use mockall::automock; use tonic::async_trait; @@ -33,7 +33,7 @@ use d_engine_proto::server::storage::SnapshotChunk; // Define a structured return value #[derive(Debug, Clone)] -pub(crate) struct AppendResults { +pub struct AppendResults { /// Whether a majority quorum is achieved (can be directly determined via /// peer_updates) pub commit_quorum_achieved: bool, @@ -44,7 +44,7 @@ pub(crate) struct AppendResults { } #[derive(Debug, Clone, PartialEq)] -pub(crate) struct PeerUpdate { +pub struct PeerUpdate { pub match_index: Option, pub next_index: u64, /// if peer response success @@ -90,7 +90,7 @@ pub struct ClusterUpdateResult { pub responses: Vec>, } -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-utils"), automock)] #[async_trait] pub trait Transport: Send + Sync + 'static where @@ -302,7 +302,7 @@ use tracing::warn; use crate::Error; /// As soon as task has return we should return from this function -pub(crate) async fn grpc_task_with_timeout_and_exponential_backoff( +pub async fn grpc_task_with_timeout_and_exponential_backoff( task_name: &'static str, mut task: F, policy: BackoffPolicy, diff --git a/d-engine-core/src/purge/default_executor.rs b/d-engine-core/src/purge/default_executor.rs index 5f52c4f3..f912aefa 100644 --- a/d-engine-core/src/purge/default_executor.rs +++ b/d-engine-core/src/purge/default_executor.rs @@ -33,7 +33,7 @@ impl PurgeExecutor for DefaultPurgeExecutor { } impl DefaultPurgeExecutor { - pub(crate) fn new(raft_log: Arc>) -> Self { + pub fn new(raft_log: Arc>) -> Self { DefaultPurgeExecutor { raft_log, pending_purge: None, diff --git a/d-engine-core/src/purge/default_executor_test.rs b/d-engine-core/src/purge/default_executor_test.rs index af8bb708..cba035eb 100644 --- a/d-engine-core/src/purge/default_executor_test.rs +++ b/d-engine-core/src/purge/default_executor_test.rs @@ -3,9 +3,9 @@ use std::sync::Arc; use tracing_test::traced_test; use super::*; -use crate::test_utils::MockTypeConfig; use crate::Error; use crate::MockRaftLog; +use crate::MockTypeConfig; // Assuming RaftLog is defined and can be mocked with mockall // For example, if RaftLog is a trait, you can mock it as follows: diff --git a/d-engine-core/src/purge/mod.rs b/d-engine-core/src/purge/mod.rs index 521abf67..f32aa535 100644 --- a/d-engine-core/src/purge/mod.rs +++ b/d-engine-core/src/purge/mod.rs @@ -6,12 +6,12 @@ //! maintaining Raft's safety guarantees. mod default_executor; -pub(crate) use default_executor::*; +pub use default_executor::*; #[cfg(test)] mod default_executor_test; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use mockall::automock; use tonic::async_trait; @@ -25,7 +25,7 @@ use d_engine_proto::common::LogId; /// 1. All entries up to `last_included.index` are preserved in snapshots /// 2. No concurrent modifications during purge execution /// 3. Atomic persistence of purge metadata -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-utils"), automock)] #[async_trait] pub trait PurgeExecutor: Send + Sync + 'static { /// Physically removes log entries up to specified index (inclusive) diff --git a/d-engine-core/src/raft.rs b/d-engine-core/src/raft.rs index 4480678f..14c67c32 100644 --- a/d-engine-core/src/raft.rs +++ b/d-engine-core/src/raft.rs @@ -33,7 +33,7 @@ where T: TypeConfig, { pub node_id: u32, - pub(crate) role: RaftRole, + pub role: RaftRole, pub ctx: RaftContext, // Network & Storage events @@ -41,7 +41,7 @@ where event_rx: mpsc::Receiver, // Timer - pub(crate) role_tx: mpsc::UnboundedSender, + pub role_tx: mpsc::UnboundedSender, role_rx: mpsc::UnboundedReceiver, // For business logic to apply logs into state machine @@ -51,19 +51,19 @@ where shutdown_signal: watch::Receiver<()>, // For unit test - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] test_role_transition_listener: Vec>, - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] test_raft_event_listener: Vec>, } -pub(crate) struct SignalParams { - pub(crate) role_tx: mpsc::UnboundedSender, - pub(crate) role_rx: mpsc::UnboundedReceiver, - pub(crate) event_tx: mpsc::Sender, - pub(crate) event_rx: mpsc::Receiver, - pub(crate) shutdown_signal: watch::Receiver<()>, +pub struct SignalParams { + pub role_tx: mpsc::UnboundedSender, + pub role_rx: mpsc::UnboundedReceiver, + pub event_tx: mpsc::Sender, + pub event_rx: mpsc::Receiver, + pub shutdown_signal: watch::Receiver<()>, } impl Raft @@ -71,7 +71,7 @@ where T: TypeConfig, { #[allow(clippy::too_many_arguments)] - pub(crate) fn new( + pub fn new( node_id: u32, role: RaftRole, storage: RaftStorageHandles, @@ -105,10 +105,10 @@ where shutdown_signal: signal_params.shutdown_signal, - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] test_role_transition_listener: Vec::new(), - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] test_raft_event_listener: Vec::new(), } } @@ -226,7 +226,7 @@ where /// `handle_role_event` will be responsbile to process role trasnsition and /// role state events. - pub(crate) async fn handle_role_event( + pub async fn handle_role_event( &mut self, role_event: RoleEvent, ) -> Result<()> { @@ -313,14 +313,14 @@ where Ok(()) } - pub(crate) fn register_new_commit_listener( + pub fn register_new_commit_listener( &mut self, tx: mpsc::UnboundedSender, ) { self.new_commit_listener.push(tx); } - pub(crate) fn notify_new_commit( + pub fn notify_new_commit( &self, new_commit_data: NewCommitData, ) { @@ -333,32 +333,32 @@ where } } - #[cfg(test)] - pub(crate) fn register_role_transition_listener( + #[cfg(any(test, feature = "test-utils"))] + pub fn register_role_transition_listener( &mut self, tx: mpsc::UnboundedSender, ) { self.test_role_transition_listener.push(tx); } - #[cfg(test)] - pub(crate) fn notify_role_transition(&self) { + #[cfg(any(test, feature = "test-utils"))] + pub fn notify_role_transition(&self) { let new_role_i32 = self.role.as_i32(); for tx in &self.test_role_transition_listener { tx.send(new_role_i32).expect("should succeed"); } } - #[cfg(test)] - pub(crate) fn register_raft_event_listener( + #[cfg(any(test, feature = "test-utils"))] + pub fn register_raft_event_listener( &mut self, tx: mpsc::UnboundedSender, ) { self.test_raft_event_listener.push(tx); } - #[cfg(test)] - pub(crate) fn notify_raft_event( + #[cfg(any(test, feature = "test-utils"))] + pub fn notify_raft_event( &self, event: super::TestEvent, ) { @@ -369,8 +369,8 @@ where } } - #[cfg(test)] - pub(crate) fn set_role( + #[cfg(any(test, feature = "test-utils"))] + pub fn set_role( &mut self, role: RaftRole, ) { diff --git a/d-engine-core/src/raft_context.rs b/d-engine-core/src/raft_context.rs index 45abf5e3..c4ca618e 100644 --- a/d-engine-core/src/raft_context.rs +++ b/d-engine-core/src/raft_context.rs @@ -1,6 +1,8 @@ use std::fmt::Debug; use std::sync::Arc; +use crate::RaftNodeConfig; +use crate::TypeConfig; use crate::alias::EOF; use crate::alias::MOF; use crate::alias::PE; @@ -9,43 +11,41 @@ use crate::alias::ROF; use crate::alias::SMHOF; use crate::alias::SMOF; use crate::alias::TROF; -use crate::RaftNodeConfig; -use crate::TypeConfig; -pub(crate) struct RaftStorageHandles { - pub(crate) raft_log: Arc>, - pub(crate) state_machine: Arc>, +pub struct RaftStorageHandles { + pub raft_log: Arc>, + pub state_machine: Arc>, } -pub(crate) struct RaftCoreHandlers { - pub(crate) election_handler: EOF, - pub(crate) replication_handler: REPOF, - pub(crate) state_machine_handler: Arc>, +pub struct RaftCoreHandlers { + pub election_handler: EOF, + pub replication_handler: REPOF, + pub state_machine_handler: Arc>, // Raft Log Purge Executor - pub(crate) purge_executor: Arc>, + pub purge_executor: Arc>, } -pub(crate) struct RaftContext +pub struct RaftContext where T: TypeConfig, { - pub(crate) node_id: u32, + pub node_id: u32, // Storages - pub(crate) storage: RaftStorageHandles, + pub storage: RaftStorageHandles, // Network - pub(crate) transport: Arc>, + pub transport: Arc>, // Cluster Membership - pub(crate) membership: Arc>, + pub membership: Arc>, // Handlers - pub(crate) handlers: RaftCoreHandlers, + pub handlers: RaftCoreHandlers, // RaftNodeConfig - pub(crate) node_config: Arc, + pub node_config: Arc, } impl RaftContext @@ -91,7 +91,7 @@ where &self.membership } - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] pub fn set_membership( &mut self, membership: Arc>, @@ -99,7 +99,7 @@ where self.membership = membership; } - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] pub fn set_transport( &mut self, transport: Arc>, diff --git a/d-engine-core/src/raft_role/candidate_state.rs b/d-engine-core/src/raft_role/candidate_state.rs index 61b5eacc..5a30f836 100644 --- a/d-engine-core/src/raft_role/candidate_state.rs +++ b/d-engine-core/src/raft_role/candidate_state.rs @@ -62,7 +62,7 @@ pub struct CandidateState { /// Note: /// Even though candidates don’t purge logs, they must keep last_purged_index from the /// follower. Otherwise, the system may lose purge info and get into an inconsistent state. - pub(super) last_purged_index: Option, + pub last_purged_index: Option, // -- Election Timing -- /// Election timeout manager @@ -610,7 +610,7 @@ impl CandidateState { } } - pub(super) fn send_become_follower_event( + pub fn send_become_follower_event( &self, role_tx: &mpsc::UnboundedSender, ) -> Result<()> { @@ -621,7 +621,7 @@ impl CandidateState { }) } - pub(super) fn send_replay_raft_event( + pub fn send_replay_raft_event( &self, role_tx: &mpsc::UnboundedSender, raft_event: RaftEvent, @@ -634,7 +634,7 @@ impl CandidateState { }) } - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] pub fn new( node_id: u32, node_config: Arc, diff --git a/d-engine-core/src/raft_role/follower_state.rs b/d-engine-core/src/raft_role/follower_state.rs index 532de443..3d2a5f66 100644 --- a/d-engine-core/src/raft_role/follower_state.rs +++ b/d-engine-core/src/raft_role/follower_state.rs @@ -63,7 +63,7 @@ pub struct FollowerState { // -- Log Compaction & Purge -- /// === Persistent State === /// Last physically purged log index (inclusive) - pub(super) last_purged_index: Option, + pub last_purged_index: Option, // -- Cluster Configuration -- /// Node configuration (shared immutable reference) @@ -665,7 +665,7 @@ impl FollowerState { /// - Critical for follower's log matching property during reelections /// - Prevents "phantom entries" when combined with §5.4.2 election restriction #[instrument(skip(self))] - pub(super) fn can_purge_logs( + pub fn can_purge_logs( &self, last_purge_index: Option, last_included_in_request: LogId, diff --git a/d-engine-core/src/raft_role/leader_state.rs b/d-engine-core/src/raft_role/leader_state.rs index a0140a46..c3360bd6 100644 --- a/d-engine-core/src/raft_role/leader_state.rs +++ b/d-engine-core/src/raft_role/leader_state.rs @@ -96,13 +96,13 @@ use d_engine_proto::server::storage::SnapshotMetadata; // Supporting data structures #[derive(Debug, Clone)] -pub(crate) struct PendingPromotion { - pub(super) node_id: u32, - pub(super) ready_since: Instant, +pub struct PendingPromotion { + pub node_id: u32, + pub ready_since: Instant, } impl PendingPromotion { - pub(crate) fn new( + pub fn new( node_id: u32, ready_since: Instant, ) -> Self { @@ -129,7 +129,7 @@ pub struct LeaderState { /// For each server (node_id), index of the next log entry to send to that server /// /// Raft Paper: §5.3 Figure 2 (nextIndex) - pub(super) next_index: HashMap, + pub next_index: HashMap, /// === Volatile State === /// For each server (node_id), index of highest log entry known to be replicated @@ -150,7 +150,7 @@ pub struct LeaderState { /// /// The actual log purge is performed by a background task, which may be delayed /// due to resource constraints or retry mechanisms. - pub(super) scheduled_purge_upto: Option, + pub scheduled_purge_upto: Option, /// === Persistent State (MUST be on disk) === /// The last log position that has been **physically removed** from stable storage. @@ -162,17 +162,17 @@ pub struct LeaderState { /// Raft safety invariant: /// Any log entry with index ≤ `last_purged_index` is guaranteed to be /// reflected in the latest snapshot. - pub(super) last_purged_index: Option, + pub last_purged_index: Option, /// === Volatile State === /// Peer purge progress tracking for flow control /// /// Key: Peer node ID /// Value: Last confirmed purge index from peer - pub(super) peer_purge_progress: HashMap, + pub peer_purge_progress: HashMap, /// Record if there is on-going snapshot creation activity - pub(super) snapshot_in_progress: AtomicBool, + pub snapshot_in_progress: AtomicBool, // -- Request Processing -- /// Batched proposal buffer for client requests @@ -216,10 +216,10 @@ pub struct LeaderState { /// /// Rationale: Avoiding frequent full scans improves batching efficiency and reduces CPU spikes /// in high-load environments (particularly crucial for RocketMQ-on-DLedger workflows). - pub(super) next_membership_maintenance_check: Instant, + pub next_membership_maintenance_check: Instant, /// Queue of learners that have caught up and are pending promotion to voter. - pub(super) pending_promotions: VecDeque, + pub pending_promotions: VecDeque, /// Lease timestamp for LeaseRead policy /// Tracks when leadership was last confirmed with quorum @@ -1219,7 +1219,7 @@ impl LeaderState { /// # Params /// - `execute_now`: should this propose been executed immediatelly. e.g. /// enforce_quorum_consensus expected to be executed immediatelly - pub(crate) async fn process_raft_request( + pub async fn process_raft_request( &mut self, raft_request_with_signal: RaftRequestWithSignal, ctx: &RaftContext, @@ -1283,7 +1283,7 @@ impl LeaderState { /// - Client response: `ProposeFailed` /// - State update: none /// - Return: original error - pub(super) async fn process_batch( + pub async fn process_batch( &mut self, batch: VecDeque, role_tx: &mpsc::UnboundedSender, @@ -1525,7 +1525,7 @@ impl LeaderState { } #[allow(dead_code)] - pub(super) async fn batch_promote_learners( + pub async fn batch_promote_learners( &mut self, ready_learners_ids: Vec, ctx: &RaftContext, @@ -1637,7 +1637,7 @@ impl LeaderState { (false, current_commit_index) } - pub(crate) fn ensure_state_machine_upto_commit_index( + pub fn ensure_state_machine_upto_commit_index( &self, state_machine_handler: &Arc>, last_applied: u64, @@ -1749,7 +1749,7 @@ impl LeaderState { /// - Design differs from followers by requiring full cluster confirmation (Raft extension for /// enhanced durability) #[instrument(skip(self))] - pub(super) fn can_purge_logs( + pub fn can_purge_logs( &self, last_purge_index: Option, last_included_in_snapshot: LogId, @@ -1766,7 +1766,7 @@ impl LeaderState { && self.peer_purge_progress.values().all(|&v| v >= last_included_in_snapshot.index) } - pub(super) async fn handle_join_cluster( + pub async fn handle_join_cluster( &mut self, join_request: JoinRequest, sender: MaybeCloneOneshotSender>, @@ -1886,8 +1886,8 @@ impl LeaderState { Err(error) } - #[cfg(test)] - pub(crate) fn new( + #[cfg(any(test, feature = "test-utils"))] + pub fn new( node_id: u32, node_config: Arc, ) -> Self { @@ -1926,7 +1926,7 @@ impl LeaderState { } } - pub(super) async fn trigger_background_snapshot( + pub async fn trigger_background_snapshot( node_id: u32, metadata: SnapshotMetadata, state_machine_handler: Arc>, @@ -1973,7 +1973,7 @@ impl LeaderState { } /// Processes all pending promotions while respecting the cluster's odd-node constraint - pub(super) async fn process_pending_promotions( + pub async fn process_pending_promotions( &mut self, ctx: &RaftContext, role_tx: &mpsc::UnboundedSender, @@ -2118,7 +2118,7 @@ impl LeaderState { /// Periodic check triggered every ~30s in the worst-case scenario /// using priority-based lazy scheduling. Actual average frequency /// is inversely proportional to system load. - pub(super) async fn conditionally_purge_stale_learners( + pub async fn conditionally_purge_stale_learners( &mut self, ctx: &RaftContext, ) -> Result<()> { @@ -2225,7 +2225,7 @@ impl LeaderState { Ok(()) } - pub(super) fn reset_next_membership_maintenance_check( + pub fn reset_next_membership_maintenance_check( &mut self, membership_maintenance_interval: Duration, ) { @@ -2233,7 +2233,7 @@ impl LeaderState { } /// FINRA Rule 4370-approved remediation - pub(super) async fn handle_stale_learner( + pub async fn handle_stale_learner( &mut self, node_id: u32, ctx: &RaftContext, @@ -2256,7 +2256,7 @@ impl LeaderState { } /// Check if current lease is still valid for LeaseRead policy - pub(super) fn is_lease_valid( + pub fn is_lease_valid( &self, ctx: &RaftContext, ) -> bool { @@ -2286,12 +2286,12 @@ impl LeaderState { self.lease_timestamp.store(now, std::sync::atomic::Ordering::Release); } - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] pub fn test_update_lease_timestamp(&self) { self.update_lease_timestamp(); } - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] pub fn test_set_lease_timestamp( &self, timestamp: u64, @@ -2361,7 +2361,7 @@ impl Debug for LeaderState { /// - `available`: number of ready learners pending promotion /// /// Returns the maximum number of nodes to promote (0 if no safe promotion exists) -pub(super) fn calculate_safe_batch_size( +pub fn calculate_safe_batch_size( current: usize, available: usize, ) -> usize { diff --git a/d-engine-core/src/raft_role/learner_state.rs b/d-engine-core/src/raft_role/learner_state.rs index 2d769dff..1f827dd2 100644 --- a/d-engine-core/src/raft_role/learner_state.rs +++ b/d-engine-core/src/raft_role/learner_state.rs @@ -554,7 +554,7 @@ impl LearnerState { } } - pub(crate) async fn broadcast_discovery( + pub async fn broadcast_discovery( &self, membership: Arc>, ctx: &RaftContext, @@ -627,7 +627,7 @@ impl LearnerState { } /// @return Option<(leader_id, Channel)> - pub(super) async fn select_valid_leader( + pub async fn select_valid_leader( &self, responses: Vec, ) -> Option { diff --git a/d-engine-core/src/raft_role/mod.rs b/d-engine-core/src/raft_role/mod.rs index 2803490e..70644627 100644 --- a/d-engine-core/src/raft_role/mod.rs +++ b/d-engine-core/src/raft_role/mod.rs @@ -185,7 +185,7 @@ impl RaftRole { #[allow(dead_code)] #[inline] - pub(crate) fn as_i32(&self) -> i32 { + pub fn as_i32(&self) -> i32 { match self { RaftRole::Follower(_) => 0, RaftRole::Candidate(_) => 1, @@ -223,29 +223,29 @@ impl RaftRole { self.state().is_learner() } - pub(crate) fn current_term(&self) -> u64 { + pub fn current_term(&self) -> u64 { self.state().current_term() } #[allow(dead_code)] - #[cfg(test)] - pub(crate) fn voted_for(&self) -> Result> { + #[cfg(any(test, feature = "test-utils"))] + pub fn voted_for(&self) -> Result> { self.state().voted_for() } #[allow(dead_code)] - #[cfg(test)] - pub(crate) fn commit_index(&self) -> u64 { + #[cfg(any(test, feature = "test-utils"))] + pub fn commit_index(&self) -> u64 { self.state().commit_index() } - #[cfg(test)] - pub(crate) fn match_index( + #[cfg(any(test, feature = "test-utils"))] + pub fn match_index( &self, node_id: u32, ) -> Option { self.state().match_index(node_id) } - #[cfg(test)] - pub(crate) fn next_index( + #[cfg(any(test, feature = "test-utils"))] + pub fn next_index( &self, node_id: u32, ) -> Option { @@ -285,7 +285,7 @@ impl RaftRole { self.state_mut().handle_raft_event(raft_event, ctx, role_tx).await } - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] pub fn follower_role_i32() -> i32 { 0 } @@ -339,7 +339,7 @@ impl<'de> Deserialize<'de> for HardState { } #[derive(Debug, PartialEq)] -pub(crate) enum QuorumVerificationResult { +pub enum QuorumVerificationResult { Success, // Leadership confirmation successful LeadershipLost, // Confirmation of leadership loss (need to abdicate) RetryRequired, // Retry required (leadership still exists) diff --git a/d-engine-core/src/raft_role/role_state.rs b/d-engine-core/src/raft_role/role_state.rs index de96940a..112bc600 100644 --- a/d-engine-core/src/raft_role/role_state.rs +++ b/d-engine-core/src/raft_role/role_state.rs @@ -32,7 +32,7 @@ use d_engine_proto::server::replication::AppendEntriesRequest; use d_engine_proto::server::replication::AppendEntriesResponse; #[async_trait] -pub(crate) trait RaftRoleState: Send + Sync + 'static { +pub trait RaftRoleState: Send + Sync + 'static { type T: TypeConfig; //--- For sharing state behaviors diff --git a/d-engine-core/src/replication/mod.rs b/d-engine-core/src/replication/mod.rs index f636c72a..83268ee0 100644 --- a/d-engine-core/src/replication/mod.rs +++ b/d-engine-core/src/replication/mod.rs @@ -19,7 +19,7 @@ use std::collections::HashMap; use std::sync::Arc; use dashmap::DashMap; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use mockall::automock; use tonic::Status; use tonic::async_trait; @@ -56,7 +56,7 @@ pub struct AppendResponseWithUpdates { } /// Core replication protocol operations -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-utils"), automock)] #[async_trait] pub trait ReplicationCore: Send + Sync + 'static where diff --git a/d-engine-core/src/replication/replication_handler.rs b/d-engine-core/src/replication/replication_handler.rs index 931a4d3d..4ddc288e 100644 --- a/d-engine-core/src/replication/replication_handler.rs +++ b/d-engine-core/src/replication/replication_handler.rs @@ -513,11 +513,11 @@ where } #[derive(Debug)] -pub(super) struct ReplicationData { - pub(super) leader_last_index_before: u64, - pub(super) current_term: u64, - pub(super) commit_index: u64, - pub(super) peer_next_indices: HashMap, +pub struct ReplicationData { + pub leader_last_index_before: u64, + pub current_term: u64, + pub commit_index: u64, + pub peer_next_indices: HashMap, } impl ReplicationHandler @@ -533,7 +533,7 @@ where /// Generate a new log entry /// including insert them into local raft log - pub(super) async fn generate_new_entries( + pub async fn generate_new_entries( &self, entry_payloads: Vec, current_term: u64, @@ -586,7 +586,7 @@ where } /// Prepare the items that need to be synchronized for each node - pub(super) fn prepare_peer_entries( + pub fn prepare_peer_entries( &self, new_entries: &[Entry], data: &ReplicationData, @@ -603,7 +603,7 @@ where } /// Build an append request for a single node - pub(super) fn build_append_request( + pub fn build_append_request( &self, raft_log: &Arc>, peer_id: u32, @@ -663,7 +663,7 @@ where /// /// # Returns /// A vector of EntryPayload containing the serialized commands -pub(crate) fn client_command_to_entry_payloads(commands: Vec) -> Vec { +pub fn client_command_to_entry_payloads(commands: Vec) -> Vec { commands .into_iter() .map(|cmd| { diff --git a/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs index 021dd118..20f76696 100644 --- a/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs +++ b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs @@ -34,11 +34,11 @@ use crate::MockRaftLog; use crate::MockReplicationCore; use crate::MockSnapshotPolicy; use crate::MockStateMachine; +use crate::MockTypeConfig; use crate::SnapshotError; use crate::StateUpdate; use crate::StorageError; use crate::test_utils::MockBuilder; -use crate::test_utils::MockTypeConfig; use crate::test_utils::crate_test_snapshot_stream; use crate::test_utils::create_test_chunk; use crate::test_utils::create_test_compressed_snapshot; diff --git a/d-engine-core/src/state_machine_handler/mod.rs b/d-engine-core/src/state_machine_handler/mod.rs index c3cae899..6b953ed6 100644 --- a/d-engine-core/src/state_machine_handler/mod.rs +++ b/d-engine-core/src/state_machine_handler/mod.rs @@ -47,11 +47,10 @@ mod default_state_machine_handler_test; #[cfg(test)] mod snapshot_assembler_test; -use std::sync::Arc; - use futures::stream::BoxStream; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use mockall::automock; +use std::sync::Arc; use tonic::async_trait; use super::NewCommitData; @@ -65,7 +64,7 @@ use d_engine_proto::server::storage::PurgeLogResponse; use d_engine_proto::server::storage::SnapshotChunk; use d_engine_proto::server::storage::SnapshotMetadata; -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-utils"), automock)] #[async_trait] pub trait StateMachineHandler: Send + Sync + 'static where diff --git a/d-engine-core/src/state_machine_handler/snapshot_policy/log_size.rs b/d-engine-core/src/state_machine_handler/snapshot_policy/log_size.rs index 08bb0a4a..bbccc16c 100644 --- a/d-engine-core/src/state_machine_handler/snapshot_policy/log_size.rs +++ b/d-engine-core/src/state_machine_handler/snapshot_policy/log_size.rs @@ -68,7 +68,7 @@ impl SnapshotPolicy for LogSizePolicy { } impl LogSizePolicy { - pub(crate) fn new( + pub fn new( threshold: u64, cooldown: Duration, ) -> Self { diff --git a/d-engine-core/src/state_machine_handler/snapshot_policy/mod.rs b/d-engine-core/src/state_machine_handler/snapshot_policy/mod.rs index d4252396..a733b890 100644 --- a/d-engine-core/src/state_machine_handler/snapshot_policy/mod.rs +++ b/d-engine-core/src/state_machine_handler/snapshot_policy/mod.rs @@ -6,7 +6,7 @@ mod log_size; mod time_based; #[allow(unused)] pub(crate) use composite::*; -pub(crate) use log_size::*; +pub use log_size::*; #[allow(unused)] pub(crate) use time_based::*; @@ -20,10 +20,10 @@ mod log_size_test; #[cfg(test)] mod time_based_test; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use mockall::automock; -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-utils"), automock)] pub trait SnapshotPolicy: Send + Sync { fn should_trigger( &self, diff --git a/d-engine-core/src/storage/raft_log.rs b/d-engine-core/src/storage/raft_log.rs index 6896f175..012407f7 100644 --- a/d-engine-core/src/storage/raft_log.rs +++ b/d-engine-core/src/storage/raft_log.rs @@ -14,12 +14,13 @@ use crate::Result; use d_engine_proto::common::Entry; use d_engine_proto::common::LogId; -#[cfg(test)] + +#[cfg(any(test, feature = "test-utils"))] use mockall::automock; use std::ops::RangeInclusive; use tonic::async_trait; -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-utils"), automock)] #[async_trait] pub trait RaftLog: Send + Sync + 'static { // ========================================================================= diff --git a/d-engine-core/src/storage/state_machine.rs b/d-engine-core/src/storage/state_machine.rs index d6e34cb6..40329540 100644 --- a/d-engine-core/src/storage/state_machine.rs +++ b/d-engine-core/src/storage/state_machine.rs @@ -1,16 +1,17 @@ #![doc = include_str!("../../../d-engine-docs/src/docs/server_guide/customize-state-machine.md")] use bytes::Bytes; -#[cfg(test)] + +#[cfg(any(test, feature = "test-utils"))] use mockall::automock; -use tonic::async_trait; use crate::Error; use d_engine_proto::common::Entry; use d_engine_proto::common::LogId; use d_engine_proto::server::storage::SnapshotMetadata; +use tonic::async_trait; -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-utils"), automock)] #[async_trait] pub trait StateMachine: Send + Sync + 'static { /// Starts the state machine service. diff --git a/d-engine-core/src/storage/storage_engine.rs b/d-engine-core/src/storage/storage_engine.rs index 2f31f3c7..dc652f1a 100644 --- a/d-engine-core/src/storage/storage_engine.rs +++ b/d-engine-core/src/storage/storage_engine.rs @@ -3,7 +3,7 @@ use std::ops::RangeInclusive; use std::sync::Arc; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use mockall::automock; use tonic::async_trait; @@ -33,7 +33,7 @@ pub trait StorageEngine: Send + Sync + 'static { fn meta_store(&self) -> Arc; } -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-utils"), automock)] #[async_trait] pub trait LogStore: Send + Sync + 'static { /// Batch persist entries into disk (optimized for sequential writes) @@ -93,7 +93,7 @@ pub trait LogStore: Send + Sync + 'static { } /// Metadata storage operations -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-utils"), automock)] #[async_trait] pub trait MetaStore: Send + Sync + 'static { /// Atomically persist hard state (current term and votedFor) diff --git a/d-engine-core/src/test_utils/mock/mock_raft_builder.rs b/d-engine-core/src/test_utils/mock/mock_raft_builder.rs index 66436600..82e35826 100644 --- a/d-engine-core/src/test_utils/mock/mock_raft_builder.rs +++ b/d-engine-core/src/test_utils/mock/mock_raft_builder.rs @@ -9,7 +9,6 @@ use tokio::sync::watch; use tracing::error; use tracing::trace; -use super::MockTypeConfig; use crate::ElectionConfig; use crate::MockElectionCore; use crate::MockMembership; @@ -32,6 +31,7 @@ use crate::RoleEvent; use crate::SignalParams; use crate::StateMachine; use crate::follower_state::FollowerState; +use crate::mock_type_config::MockTypeConfig; use d_engine_proto::common::LogId; use d_engine_proto::server::cluster::ClusterMembership; @@ -81,7 +81,7 @@ impl MockBuilder { } } - pub(crate) fn build_context(self) -> RaftContext { + pub fn build_context(self) -> RaftContext { let ( raft_log, state_machine, diff --git a/d-engine-core/src/test_utils/mock/mod.rs b/d-engine-core/src/test_utils/mock/mod.rs index d8c85d28..860d0be0 100644 --- a/d-engine-core/src/test_utils/mock/mod.rs +++ b/d-engine-core/src/test_utils/mock/mod.rs @@ -41,21 +41,17 @@ mod mock_raft_builder; mod mock_rpc; mod mock_rpc_service; -mod mock_storage_engine; -mod mock_type_config; pub use mock_raft_builder::*; pub use mock_rpc::*; pub use mock_rpc_service::*; -pub use mock_storage_engine::*; -pub use mock_type_config::*; use super::node_config; -use crate::RaftContext; +use crate::{RaftContext, mock_type_config::MockTypeConfig}; use d_engine_proto::server::cluster::NodeMeta; use tokio::sync::watch; -pub(crate) fn mock_raft_context( +pub fn mock_raft_context( db_path: &str, shutdown_signal: watch::Receiver<()>, peers_meta_option: Option>, diff --git a/d-engine-core/src/test_utils/mod.rs b/d-engine-core/src/test_utils/mod.rs index a56c772f..a40b45ea 100644 --- a/d-engine-core/src/test_utils/mod.rs +++ b/d-engine-core/src/test_utils/mod.rs @@ -8,7 +8,7 @@ mod snapshot; pub use common::*; pub use entry_builder::*; pub use mock::*; -pub(crate) use snapshot::*; +pub use snapshot::*; pub fn node_config(db_path: &str) -> crate::RaftNodeConfig { let mut s = diff --git a/d-engine-core/src/test_utils/snapshot.rs b/d-engine-core/src/test_utils/snapshot.rs index a54a350e..bea609bb 100644 --- a/d-engine-core/src/test_utils/snapshot.rs +++ b/d-engine-core/src/test_utils/snapshot.rs @@ -50,7 +50,7 @@ pub(crate) fn create_snapshot_stream( ) } -pub(crate) fn crate_test_snapshot_stream(chunks: Vec) -> tonic::Streaming +pub fn crate_test_snapshot_stream(chunks: Vec) -> tonic::Streaming where T: prost::Message + Default + 'static, { @@ -163,7 +163,7 @@ where } /// Helper to create valid test chunk -pub(crate) fn create_test_chunk( +pub fn create_test_chunk( seq: u32, data: &[u8], leader_term: u64, diff --git a/d-engine-core/src/utils/stream.rs b/d-engine-core/src/utils/stream.rs index 3aa6ebcd..86deffc8 100644 --- a/d-engine-core/src/utils/stream.rs +++ b/d-engine-core/src/utils/stream.rs @@ -9,8 +9,8 @@ use http_body_util::BodyExt; use http_body_util::StreamBody; use prost::Message; use tokio::sync::mpsc; -use tokio_stream::wrappers::ReceiverStream; use tokio_stream::StreamExt; +use tokio_stream::wrappers::ReceiverStream; use tonic::Code; use tonic::Status; use tonic::Streaming; // Adjust path as needed @@ -47,7 +47,7 @@ pub fn encode_varint( } impl GrpcStreamDecoder { - pub(crate) fn new() -> Self { + pub fn new() -> Self { GrpcStreamDecoder { _marker: PhantomData, } diff --git a/d-engine-runtime/Cargo.toml b/d-engine-runtime/Cargo.toml index 45173dcc..690ad591 100644 --- a/d-engine-runtime/Cargo.toml +++ b/d-engine-runtime/Cargo.toml @@ -1,8 +1,11 @@ [package] -name = "d-engine-server" +name = "d-engine-runtime" version = "0.2.0" edition = "2024" +[features] +test-utils = ["mockall", "tokio-tar", "async-compression", "nanoid"] + [dependencies] d-engine-proto = { workspace = true } d-engine-core = { workspace = true } @@ -15,21 +18,37 @@ tracing = { workspace = true } tempfile = { workspace = true } futures = { workspace = true } prost = { workspace = true } - +bincode = "1.3" rocksdb = { version = "0.24.0", optional = true } config = { version = "0.14.0", default-features = false, features = ["toml"] } arc-swap = "1.7.1" dashmap = "5.5.3" # use tokio_util::sync::CancellationToken; tokio-util = "0.7.11" - +tokio-stream = "0.1.16" # certification rcgen = { version = "0.13", features = ["pem"] } +crc32fast = "1.4.2" +# used in stream +http-body = "1.0" +http-body-util = "0.1.3" +tonic-health = "0.12.3" +parking_lot = "0.12.3" +crossbeam = "0.8" +crossbeam-skiplist = "0.1" +mockall = { version = "0.12.1", optional = true } +nanoid = { version = "0.4.0", optional = true } +tokio-tar = { version = "0.3", optional = true } +async-compression = { version = "0.4", features = ["tokio", "gzip"], optional = true} + +metrics = { version = "0.24", features = [] } + [dev-dependencies] +d-engine-core = { workspace = true, features = ["test-utils"] } serial_test = "3.2.0" temp-env = "0.3.6" - tracing-test = "0.2" -tonic-health = "0.12.3" tokio-stream = "0.1.16" +uuid = { version = "1", features = ["v4"] } +tokio = { version = "1", features = ["test-util", "process"] } diff --git a/d-engine-runtime/src/lib.rs b/d-engine-runtime/src/lib.rs index 609c8c29..bb609ba2 100644 --- a/d-engine-runtime/src/lib.rs +++ b/d-engine-runtime/src/lib.rs @@ -20,5 +20,7 @@ pub mod utils; #[doc(hidden)] pub use utils::*; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] pub mod test_utils; +#[cfg(any(test, feature = "test-utils"))] +pub use test_utils::*; diff --git a/d-engine-runtime/src/membership/raft_membership_test.rs b/d-engine-runtime/src/membership/raft_membership_test.rs index 94b7fbfc..673ad9b0 100644 --- a/d-engine-runtime/src/membership/raft_membership_test.rs +++ b/d-engine-runtime/src/membership/raft_membership_test.rs @@ -10,10 +10,10 @@ use d_engine_core::Error; use crate::RaftTypeConfig; use crate::test_utils::MockNode; use crate::test_utils::MockRpcService; -use crate::test_utils::MockStorageEngine; -use crate::test_utils::MockTypeConfig; use d_engine_core::Membership; use d_engine_core::MembershipError; +use d_engine_core::MockStorageEngine; +use d_engine_core::MockTypeConfig; use d_engine_core::RaftNodeConfig; use d_engine_core::ensure_safe_join; use d_engine_proto::common::AddNode; @@ -930,10 +930,10 @@ mod check_cluster_is_ready_test { use tokio::sync::oneshot; use super::*; - use crate::MockStateMachine; use crate::test_utils::MockNode; use crate::test_utils::MockRpcService; - use crate::test_utils::MockStorageEngine; + use d_engine_core::MockStateMachine; + use d_engine_core::MockStorageEngine; /// Case 1: Test all peers are healthy #[tokio::test] diff --git a/d-engine-runtime/src/network/connection_cache.rs b/d-engine-runtime/src/network/connection_cache.rs index 5a64d18b..b1409f4e 100644 --- a/d-engine-runtime/src/network/connection_cache.rs +++ b/d-engine-runtime/src/network/connection_cache.rs @@ -7,8 +7,8 @@ use tonic::transport::Endpoint; use tracing::debug; use tracing::trace; -use crate::ConnectionType; -use crate::NetworkConfig; +use d_engine_core::ConnectionType; +use d_engine_core::NetworkConfig; use d_engine_core::NetworkError; use d_engine_core::Result; diff --git a/d-engine-runtime/src/network/connection_cache_test.rs b/d-engine-runtime/src/network/connection_cache_test.rs index f706502d..96f04512 100644 --- a/d-engine-runtime/src/network/connection_cache_test.rs +++ b/d-engine-runtime/src/network/connection_cache_test.rs @@ -8,9 +8,9 @@ use super::*; use crate::net::address_str; use crate::test_utils::MockRpcService; use crate::test_utils::{self}; -use crate::ConnectionParams; -use crate::ConnectionType; -use crate::NetworkConfig; +use d_engine_core::ConnectionParams; +use d_engine_core::ConnectionType; +use d_engine_core::NetworkConfig; // Helper to create test config fn test_config() -> NetworkConfig { diff --git a/d-engine-runtime/src/network/grpc/grpc_raft_service.rs b/d-engine-runtime/src/network/grpc/grpc_raft_service.rs index 434bcce2..82ffe2ea 100644 --- a/d-engine-runtime/src/network/grpc/grpc_raft_service.rs +++ b/d-engine-runtime/src/network/grpc/grpc_raft_service.rs @@ -41,12 +41,12 @@ use d_engine_proto::server::election::raft_election_service_server::RaftElection use d_engine_proto::server::replication::AppendEntriesRequest; use d_engine_proto::server::replication::AppendEntriesResponse; use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationService; -use d_engine_proto::storage::PurgeLogRequest; -use d_engine_proto::storage::PurgeLogResponse; -use d_engine_proto::storage::SnapshotAck; -use d_engine_proto::storage::SnapshotChunk; -use d_engine_proto::storage::SnapshotResponse; -use d_engine_proto::storage::snapshot_service_server::SnapshotService; +use d_engine_proto::server::storage::PurgeLogRequest; +use d_engine_proto::server::storage::PurgeLogResponse; +use d_engine_proto::server::storage::SnapshotAck; +use d_engine_proto::server::storage::SnapshotChunk; +use d_engine_proto::server::storage::SnapshotResponse; +use d_engine_proto::server::storage::snapshot_service_server::SnapshotService; #[tonic::async_trait] impl RaftElectionService for Node diff --git a/d-engine-runtime/src/network/grpc/grpc_raft_service_test.rs b/d-engine-runtime/src/network/grpc/grpc_raft_service_test.rs index 5afaffbc..9f442c60 100644 --- a/d-engine-runtime/src/network/grpc/grpc_raft_service_test.rs +++ b/d-engine-runtime/src/network/grpc/grpc_raft_service_test.rs @@ -8,17 +8,17 @@ use tonic::Code; use tonic::Request; use tracing_test::traced_test; -use crate::AppendResponseWithUpdates; -use crate::AppendResults; -use crate::MockElectionCore; -use crate::MockReplicationCore; -use crate::RaftNodeConfig; -use crate::StateUpdate; -use crate::convert::safe_kv_bytes; use crate::test_utils::MockBuilder; -use crate::test_utils::MockTypeConfig; use crate::test_utils::mock_node; +use d_engine_core::AppendResponseWithUpdates; +use d_engine_core::AppendResults; +use d_engine_core::MockElectionCore; use d_engine_core::MockMembership; +use d_engine_core::MockReplicationCore; +use d_engine_core::MockTypeConfig; +use d_engine_core::RaftNodeConfig; +use d_engine_core::StateUpdate; +use d_engine_core::convert::safe_kv_bytes; use d_engine_proto::client::ClientReadRequest; use d_engine_proto::client::ClientWriteRequest; use d_engine_proto::client::ReadConsistencyPolicy; diff --git a/d-engine-runtime/src/network/grpc/grpc_transport.rs b/d-engine-runtime/src/network/grpc/grpc_transport.rs index 9e509349..fc4502a7 100644 --- a/d-engine-runtime/src/network/grpc/grpc_transport.rs +++ b/d-engine-runtime/src/network/grpc/grpc_transport.rs @@ -22,22 +22,22 @@ use tracing::error; use tracing::info; use tracing::warn; -use crate::AppendResult; -use crate::BackoffPolicy; -use crate::ClusterUpdateResult; -use crate::ConnectionType; -use crate::Error; -use crate::InstallSnapshotBackoffPolicy; -use crate::Membership; -use crate::RetryPolicies; -use crate::VoteResult; -use crate::alias::MOF; -use crate::grpc_task_with_timeout_and_exponential_backoff; use crate::scoped_timer::ScopedTimer; +use d_engine_core::AppendResult; +use d_engine_core::BackoffPolicy; +use d_engine_core::ClusterUpdateResult; +use d_engine_core::ConnectionType; +use d_engine_core::Error; +use d_engine_core::InstallSnapshotBackoffPolicy; +use d_engine_core::Membership; use d_engine_core::NetworkError; use d_engine_core::Result; +use d_engine_core::RetryPolicies; use d_engine_core::Transport; use d_engine_core::TypeConfig; +use d_engine_core::VoteResult; +use d_engine_core::alias::MOF; +use d_engine_core::grpc_task_with_timeout_and_exponential_backoff; use d_engine_proto::server::cluster::ClusterConfChangeRequest; use d_engine_proto::server::cluster::JoinResponse; use d_engine_proto::server::cluster::LeaderDiscoveryRequest; @@ -48,11 +48,11 @@ use d_engine_proto::server::election::raft_election_service_client::RaftElection use d_engine_proto::server::replication::AppendEntriesRequest; use d_engine_proto::server::replication::AppendEntriesResponse; use d_engine_proto::server::replication::raft_replication_service_client::RaftReplicationServiceClient; -use d_engine_proto::storage::PurgeLogRequest; -use d_engine_proto::storage::PurgeLogResponse; -use d_engine_proto::storage::SnapshotAck; -use d_engine_proto::storage::SnapshotChunk; -use d_engine_proto::storage::snapshot_service_client::SnapshotServiceClient; +use d_engine_proto::server::storage::PurgeLogRequest; +use d_engine_proto::server::storage::PurgeLogResponse; +use d_engine_proto::server::storage::SnapshotAck; +use d_engine_proto::server::storage::SnapshotChunk; +use d_engine_proto::server::storage::snapshot_service_client::SnapshotServiceClient; pub struct PeerAppender { pub(crate) sender: mpsc::Sender, @@ -453,7 +453,7 @@ where async fn join_cluster( &self, leader_id: u32, - request: crate::proto::cluster::JoinRequest, + request: d_engine_proto::server::cluster::JoinRequest, retry: BackoffPolicy, membership: Arc>, ) -> Result { diff --git a/d-engine-runtime/src/network/grpc/grpc_transport_test.rs b/d-engine-runtime/src/network/grpc/grpc_transport_test.rs index 84961d16..76ff8e46 100644 --- a/d-engine-runtime/src/network/grpc/grpc_transport_test.rs +++ b/d-engine-runtime/src/network/grpc/grpc_transport_test.rs @@ -11,19 +11,19 @@ use tonic::transport::Endpoint; use tracing_test::traced_test; use super::*; -use crate::ConnectionType; -use crate::Error; -use crate::RaftNodeConfig; -use crate::RetryPolicies; -use crate::SystemError; use crate::grpc::grpc_transport::GrpcTransport; use crate::test_utils::MockNode; -use crate::test_utils::MockTypeConfig; use crate::test_utils::crate_test_snapshot_stream; use crate::test_utils::create_test_chunk; use crate::test_utils::node_config; +use d_engine_core::ConnectionType; +use d_engine_core::Error; use d_engine_core::MockMembership; +use d_engine_core::MockTypeConfig; use d_engine_core::NetworkError; +use d_engine_core::RaftNodeConfig; +use d_engine_core::RetryPolicies; +use d_engine_core::SystemError; use d_engine_core::Transport; use d_engine_proto::common::LogId; use d_engine_proto::common::NodeRole::Candidate; @@ -38,9 +38,9 @@ use d_engine_proto::server::election::VoteRequest; use d_engine_proto::server::election::VoteResponse; use d_engine_proto::server::replication::AppendEntriesRequest; use d_engine_proto::server::replication::AppendEntriesResponse; -use d_engine_proto::storage::PurgeLogRequest; -use d_engine_proto::storage::PurgeLogResponse; -use d_engine_proto::storage::SnapshotChunk; +use d_engine_proto::server::storage::PurgeLogRequest; +use d_engine_proto::server::storage::PurgeLogResponse; +use d_engine_proto::server::storage::SnapshotChunk; fn mock_membership( peers: Vec<(u32, i32)>, //(node_id, role_i32) diff --git a/d-engine-runtime/src/network/grpc/mod.rs b/d-engine-runtime/src/network/grpc/mod.rs index 7a3717b4..f0307f6c 100644 --- a/d-engine-runtime/src/network/grpc/mod.rs +++ b/d-engine-runtime/src/network/grpc/mod.rs @@ -16,16 +16,24 @@ mod grpc_raft_service_test; #[cfg(test)] mod grpc_transport_test; -//------------------------------------------------------------------------------- -// Start RPC Server +use crate::Node; +use d_engine_core::RaftNodeConfig; +use d_engine_core::Result; +use d_engine_core::SystemError; +use d_engine_core::TlsConfig; +use d_engine_core::TypeConfig; +use d_engine_proto::client::raft_client_service_server::RaftClientServiceServer; +use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementServiceServer; +use d_engine_proto::server::election::raft_election_service_server::RaftElectionServiceServer; +use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationServiceServer; +use d_engine_proto::server::storage::snapshot_service_server::SnapshotServiceServer; +use futures::FutureExt; +use rcgen::CertifiedKey; +use rcgen::generate_simple_self_signed; use std::net::SocketAddr; use std::path::Path; use std::sync::Arc; use std::time::Duration; - -use futures::FutureExt; -use rcgen::CertifiedKey; -use rcgen::generate_simple_self_signed; use tokio::sync::watch; use tonic::codec::CompressionEncoding; use tonic::transport::Certificate; @@ -37,18 +45,6 @@ use tracing::error; use tracing::info; use tracing::warn; -use crate::Node; -use crate::RaftNodeConfig; -use crate::SystemError; -use crate::TlsConfig; -use d_engine_core::Result; -use d_engine_core::TypeConfig; -use d_engine_proto::client::raft_client_service_server::RaftClientServiceServer; -use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementServiceServer; -use d_engine_proto::server::election::raft_election_service_server::RaftElectionServiceServer; -use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationServiceServer; -use d_engine_proto::storage::snapshot_service_server::SnapshotServiceServer; - /// RPC server works for RAFT protocol /// It mainly listens on two request: Vote RPC Request and Append Entries RPC /// Request The server itself knows its peers. diff --git a/d-engine-runtime/src/network/health_checker.rs b/d-engine-runtime/src/network/health_checker.rs index 96857a75..5fb6491e 100644 --- a/d-engine-runtime/src/network/health_checker.rs +++ b/d-engine-runtime/src/network/health_checker.rs @@ -1,8 +1,8 @@ use std::time::Duration; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use mockall::automock; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use mockall::predicate::*; use tonic::async_trait; use tonic::transport::Channel; @@ -12,12 +12,12 @@ use tonic_health::pb::health_check_response::ServingStatus; use tonic_health::pb::health_client::HealthClient; use tracing::error; -use crate::NetworkConfig; use crate::net::address_str; +use d_engine_core::NetworkConfig; use d_engine_core::NetworkError; use d_engine_core::Result; -#[cfg_attr(test, automock)] +#[cfg_attr(any(test, feature = "test-utils"), automock)] #[async_trait] pub(crate) trait HealthCheckerApis { async fn check_peer_is_ready( diff --git a/d-engine-runtime/src/network/health_checker_test.rs b/d-engine-runtime/src/network/health_checker_test.rs index feee6f04..ce078278 100644 --- a/d-engine-runtime/src/network/health_checker_test.rs +++ b/d-engine-runtime/src/network/health_checker_test.rs @@ -6,8 +6,8 @@ use super::*; use crate::test_utils::MockNode; use crate::test_utils::MockRpcService; use crate::test_utils::{self}; -use crate::ConnectionParams; -use crate::NetworkConfig; +use d_engine_core::ConnectionParams; +use d_engine_core::NetworkConfig; /// Case 1: server is not ready #[tokio::test] diff --git a/d-engine-runtime/src/node/builder.rs b/d-engine-runtime/src/node/builder.rs index e2b097a6..738c0f22 100644 --- a/d-engine-runtime/src/node/builder.rs +++ b/d-engine-runtime/src/node/builder.rs @@ -48,38 +48,38 @@ use tracing::info; use super::RaftTypeConfig; use crate::BufferedRaftLog; -use crate::ClusterConfig; -use crate::CommitHandler; -use crate::CommitHandlerDependencies; -use crate::DefaultCommitHandler; -use crate::DefaultPurgeExecutor; -use crate::DefaultStateMachineHandler; -use crate::ElectionHandler; -use crate::LogSizePolicy; -use crate::NewCommitData; use crate::Node; -use crate::Raft; -use crate::RaftConfig; -use crate::RaftCoreHandlers; -use crate::RaftLog; use crate::RaftMembership; -use crate::RaftNodeConfig; -use crate::RaftRole; -use crate::RaftStorageHandles; -use crate::ReplicationHandler; -use crate::SignalParams; -use crate::StateMachine; -use crate::StorageEngine; -use crate::SystemError; -use crate::alias::MOF; -use crate::alias::SMHOF; -use crate::alias::SNP; -use crate::alias::TROF; -use crate::follower_state::FollowerState; use crate::grpc; use crate::grpc::grpc_transport::GrpcTransport; -use crate::learner_state::LearnerState; +use d_engine_core::ClusterConfig; +use d_engine_core::CommitHandler; +use d_engine_core::CommitHandlerDependencies; +use d_engine_core::DefaultCommitHandler; +use d_engine_core::DefaultPurgeExecutor; +use d_engine_core::DefaultStateMachineHandler; +use d_engine_core::ElectionHandler; +use d_engine_core::LogSizePolicy; +use d_engine_core::NewCommitData; +use d_engine_core::Raft; +use d_engine_core::RaftConfig; +use d_engine_core::RaftCoreHandlers; +use d_engine_core::RaftLog; +use d_engine_core::RaftNodeConfig; +use d_engine_core::RaftRole; +use d_engine_core::RaftStorageHandles; +use d_engine_core::ReplicationHandler; use d_engine_core::Result; +use d_engine_core::SignalParams; +use d_engine_core::StateMachine; +use d_engine_core::StorageEngine; +use d_engine_core::SystemError; +use d_engine_core::alias::MOF; +use d_engine_core::alias::SMHOF; +use d_engine_core::alias::SNP; +use d_engine_core::alias::TROF; +use d_engine_core::follower_state::FollowerState; +use d_engine_core::learner_state::LearnerState; pub enum NodeMode { Joiner, diff --git a/d-engine-runtime/src/node/builder_test.rs b/d-engine-runtime/src/node/builder_test.rs index 072ab623..108c6687 100644 --- a/d-engine-runtime/src/node/builder_test.rs +++ b/d-engine-runtime/src/node/builder_test.rs @@ -2,30 +2,30 @@ use std::path::PathBuf; use std::sync::Arc; use serial_test::serial; -use tempfile::tempdir; use tempfile::TempDir; +use tempfile::tempdir; use tokio::sync::watch; use tracing_test::traced_test; -use crate::test_utils::insert_raft_log; -use crate::test_utils::insert_state_machine; -use crate::test_utils::mock_state_machine; -use crate::test_utils::MockStorageEngine; use crate::BufferedRaftLog; -use crate::Error; use crate::FileStateMachine; use crate::FileStorageEngine; -use crate::FlushPolicy; -use crate::LogStore; -use crate::MockStateMachine; use crate::NodeBuilder; -use crate::PersistenceConfig; -use crate::PersistenceStrategy; -use crate::RaftNodeConfig; use crate::RaftTypeConfig; -use crate::StateMachine; -use crate::StorageEngine; -use crate::SystemError; +use crate::test_utils::insert_raft_log; +use crate::test_utils::insert_state_machine; +use crate::test_utils::mock_state_machine; +use d_engine_core::Error; +use d_engine_core::FlushPolicy; +use d_engine_core::LogStore; +use d_engine_core::MockStateMachine; +use d_engine_core::MockStorageEngine; +use d_engine_core::PersistenceConfig; +use d_engine_core::PersistenceStrategy; +use d_engine_core::RaftNodeConfig; +use d_engine_core::StateMachine; +use d_engine_core::StorageEngine; +use d_engine_core::SystemError; /// These components should not be initialized during builder setup; developers should have the /// highest priority to customize them first. diff --git a/d-engine-runtime/src/node/node_test.rs b/d-engine-runtime/src/node/node_test.rs index f6d5c409..69da0ff3 100644 --- a/d-engine-runtime/src/node/node_test.rs +++ b/d-engine-runtime/src/node/node_test.rs @@ -6,22 +6,22 @@ use tokio::sync::watch; use tracing::debug; use tracing_test::traced_test; -use crate::AppendResults; -use crate::Error; -use crate::Follower.into(); -use crate::Learner.into(); -use crate::MockRaftLog; -use crate::MockReplicationCore; -use crate::MockTransport; -use crate::PeerUpdate; -use crate::RaftNodeConfig; -use crate::RaftRole; -use crate::learner_state::LearnerState; use crate::test_utils::MockBuilder; -use crate::test_utils::MockTypeConfig; use crate::test_utils::mock_membership; use crate::test_utils::mock_node; +use d_engine_core::AppendResults; +use d_engine_core::Error; use d_engine_core::MockMembership; +use d_engine_core::MockRaftLog; +use d_engine_core::MockReplicationCore; +use d_engine_core::MockTransport; +use d_engine_core::MockTypeConfig; +use d_engine_core::PeerUpdate; +use d_engine_core::RaftNodeConfig; +use d_engine_core::RaftRole; +use d_engine_core::learner_state::LearnerState; +use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeRole::Learner; use d_engine_proto::common::NodeStatus; use d_engine_proto::server::cluster::JoinResponse; use d_engine_proto::server::cluster::NodeMeta; diff --git a/d-engine-runtime/src/node/type_config/raft_type_config.rs b/d-engine-runtime/src/node/type_config/raft_type_config.rs index b7bc7f74..c998948f 100644 --- a/d-engine-runtime/src/node/type_config/raft_type_config.rs +++ b/d-engine-runtime/src/node/type_config/raft_type_config.rs @@ -1,16 +1,16 @@ use std::fmt::Debug; use crate::BufferedRaftLog; -use crate::DefaultCommitHandler; -use crate::DefaultPurgeExecutor; -use crate::DefaultStateMachineHandler; -use crate::ElectionHandler; -use crate::LogSizePolicy; use crate::RaftMembership; -use crate::ReplicationHandler; -use crate::StateMachine; -use crate::StorageEngine; use crate::grpc::grpc_transport::GrpcTransport; +use d_engine_core::DefaultCommitHandler; +use d_engine_core::DefaultPurgeExecutor; +use d_engine_core::DefaultStateMachineHandler; +use d_engine_core::ElectionHandler; +use d_engine_core::LogSizePolicy; +use d_engine_core::ReplicationHandler; +use d_engine_core::StateMachine; +use d_engine_core::StorageEngine; use d_engine_core::TypeConfig; #[derive(Debug)] diff --git a/d-engine-runtime/src/storage/adaptors/file/file_engine_test.rs b/d-engine-runtime/src/storage/adaptors/file/file_engine_test.rs index f3d0ba4f..87913496 100644 --- a/d-engine-runtime/src/storage/adaptors/file/file_engine_test.rs +++ b/d-engine-runtime/src/storage/adaptors/file/file_engine_test.rs @@ -5,13 +5,13 @@ use tonic::async_trait; use tracing_test::traced_test; use super::FileStorageEngine; -use crate::storage::state_machine_test::StateMachineBuilder; -use crate::storage::state_machine_test::StateMachineTestSuite; -use crate::storage::storage_engine_test::StorageEngineBuilder; -use crate::storage::storage_engine_test::StorageEngineTestSuite; -use crate::Error; use crate::FileStateMachine; -use crate::StateMachine; +use d_engine_core::Error; +use d_engine_core::StateMachine; +use d_engine_core::state_machine_test::StateMachineBuilder; +use d_engine_core::state_machine_test::StateMachineTestSuite; +use d_engine_core::storage_engine_test::StorageEngineBuilder; +use d_engine_core::storage_engine_test::StorageEngineTestSuite; struct FileStorageEngineBuilder { temp_dir: TempDir, diff --git a/d-engine-runtime/src/storage/adaptors/file/file_state_machine.rs b/d-engine-runtime/src/storage/adaptors/file/file_state_machine.rs index 8074cadc..be4580e5 100644 --- a/d-engine-runtime/src/storage/adaptors/file/file_state_machine.rs +++ b/d-engine-runtime/src/storage/adaptors/file/file_state_machine.rs @@ -21,9 +21,9 @@ use tracing::info; use tracing::trace; use tracing::warn; -use crate::Error; -use crate::StateMachine; -use crate::StorageError; +use d_engine_core::Error; +use d_engine_core::StateMachine; +use d_engine_core::StorageError; use d_engine_proto::client::WriteCommand; use d_engine_proto::client::write_command::Delete; use d_engine_proto::client::write_command::Insert; @@ -31,7 +31,7 @@ use d_engine_proto::client::write_command::Operation; use d_engine_proto::common::Entry; use d_engine_proto::common::LogId; use d_engine_proto::common::entry_payload::Payload; -use d_engine_proto::storage::SnapshotMetadata; +use d_engine_proto::server::storage::SnapshotMetadata; type FileStateMachineDataType = RwLock>; diff --git a/d-engine-runtime/src/storage/adaptors/file/file_state_machine_test.rs b/d-engine-runtime/src/storage/adaptors/file/file_state_machine_test.rs index 57b34797..935b92c6 100644 --- a/d-engine-runtime/src/storage/adaptors/file/file_state_machine_test.rs +++ b/d-engine-runtime/src/storage/adaptors/file/file_state_machine_test.rs @@ -2,7 +2,7 @@ use bytes::Bytes; use prost::Message; use crate::FileStateMachine; -use crate::StateMachine; +use d_engine_core::StateMachine; use d_engine_proto::client::WriteCommand; use d_engine_proto::client::write_command::Insert; use d_engine_proto::client::write_command::Operation; diff --git a/d-engine-runtime/src/storage/adaptors/file/file_storage_engine.rs b/d-engine-runtime/src/storage/adaptors/file/file_storage_engine.rs index 185caf09..93145fe5 100644 --- a/d-engine-runtime/src/storage/adaptors/file/file_storage_engine.rs +++ b/d-engine-runtime/src/storage/adaptors/file/file_storage_engine.rs @@ -1,3 +1,4 @@ +use prost::Message; use std::collections::BTreeMap; use std::collections::HashMap; use std::fs::File; @@ -14,8 +15,6 @@ use std::sync::Arc; use std::sync::Mutex; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; - -use prost::Message; use tonic::async_trait; use tracing::info; @@ -223,7 +222,7 @@ impl FileLogStore { Ok(()) } - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] pub fn reset_sync(&self) -> Result<(), Error> { { let mut file = self.file_handle.lock().unwrap(); diff --git a/d-engine-runtime/src/storage/adaptors/file/file_storage_engine_test.rs b/d-engine-runtime/src/storage/adaptors/file/file_storage_engine_test.rs index 7c220541..87442c02 100644 --- a/d-engine-runtime/src/storage/adaptors/file/file_storage_engine_test.rs +++ b/d-engine-runtime/src/storage/adaptors/file/file_storage_engine_test.rs @@ -1,15 +1,14 @@ +use bytes::Bytes; use std::ops::RangeInclusive; use std::sync::Arc; - -use bytes::Bytes; use tempfile::TempDir; use tracing_test::traced_test; use super::*; -use crate::HardState; -use crate::LogStore; -use crate::MetaStore; -use crate::StorageEngine; +use d_engine_core::HardState; +use d_engine_core::LogStore; +use d_engine_core::MetaStore; +use d_engine_core::StorageEngine; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; use d_engine_proto::common::LogId; diff --git a/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs b/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs index a21ed315..c8bbe456 100644 --- a/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs +++ b/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs @@ -9,11 +9,11 @@ use tracing_test::traced_test; use uuid::Uuid; use super::*; -use crate::Error; -use crate::LogStore; -use crate::StorageEngine; -use crate::storage::storage_engine_test::StorageEngineBuilder; -use crate::storage::storage_engine_test::StorageEngineTestSuite; +use d_engine_core::Error; +use d_engine_core::LogStore; +use d_engine_core::StorageEngine; +use d_engine_core::storage_engine_test::StorageEngineBuilder; +use d_engine_core::storage_engine_test::StorageEngineTestSuite; use d_engine_proto::client::write_command::Insert; use d_engine_proto::common::Entry; @@ -97,19 +97,19 @@ async fn test_rocksdb_performance() -> Result<(), Error> { Ok(()) } -fn create_test_command_payload(index: u64) -> crate::proto::common::EntryPayload { +fn create_test_command_payload(index: u64) -> d_engine_proto::common::EntryPayload { // Create a simple insert command let key = Bytes::from(format!("key_{index}").into_bytes()); let value = Bytes::from(format!("value_{index}").into_bytes()); let insert = Insert { key, value }; - let operation = crate::proto::client::write_command::Operation::Insert(insert); - let write_cmd = crate::proto::client::WriteCommand { + let operation = d_engine_proto::client::write_command::Operation::Insert(insert); + let write_cmd = d_engine_proto::client::WriteCommand { operation: Some(operation), }; - crate::proto::common::EntryPayload { - payload: Some(crate::proto::common::entry_payload::Payload::Command( + d_engine_proto::common::EntryPayload { + payload: Some(d_engine_proto::common::entry_payload::Payload::Command( write_cmd.encode_to_vec().into(), )), } diff --git a/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs b/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs index 0fef39a9..fe6b0785 100644 --- a/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs +++ b/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs @@ -19,9 +19,9 @@ use tracing::info; use tracing::instrument; use tracing::warn; -use crate::Error; -use crate::StateMachine; -use crate::StorageError; +use d_engine_core::Error; +use d_engine_core::StateMachine; +use d_engine_core::StorageError; use d_engine_proto::client::WriteCommand; use d_engine_proto::client::write_command::Delete; use d_engine_proto::client::write_command::Insert; @@ -29,7 +29,7 @@ use d_engine_proto::client::write_command::Operation; use d_engine_proto::common::Entry; use d_engine_proto::common::LogId; use d_engine_proto::common::entry_payload::Payload; -use d_engine_proto::storage::SnapshotMetadata; +use d_engine_proto::server::storage::SnapshotMetadata; const STATE_MACHINE_CF: &str = "state_machine"; const STATE_MACHINE_META_CF: &str = "state_machine_meta"; diff --git a/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs b/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs index 6a3384a2..cfe693c2 100644 --- a/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs +++ b/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs @@ -14,12 +14,12 @@ use rocksdb::WriteBatch; use tonic::async_trait; use tracing::instrument; -use crate::Error; -use crate::HardState; -use crate::LogStore; -use crate::MetaStore; -use crate::StorageEngine; -use crate::StorageError; +use d_engine_core::Error; +use d_engine_core::HardState; +use d_engine_core::LogStore; +use d_engine_core::MetaStore; +use d_engine_core::StorageEngine; +use d_engine_core::StorageError; use d_engine_proto::common::Entry; use d_engine_proto::common::LogId; diff --git a/d-engine-runtime/src/storage/buffered/buffered_raft_log.rs b/d-engine-runtime/src/storage/buffered/buffered_raft_log.rs index b044fb3a..e176f9a3 100644 --- a/d-engine-runtime/src/storage/buffered/buffered_raft_log.rs +++ b/d-engine-runtime/src/storage/buffered/buffered_raft_log.rs @@ -38,20 +38,20 @@ use tracing::error; use tracing::trace; use tracing::warn; -use crate::Error; -use crate::FlushPolicy; -use crate::HardState; -use crate::LogStore; -use crate::MetaStore; -use crate::NetworkError; -use crate::PersistenceConfig; -use crate::PersistenceStrategy; -use crate::RaftLog; -use crate::Result; -use crate::StorageEngine; -use crate::TypeConfig; -use crate::alias::SOF; use crate::scoped_timer::ScopedTimer; +use d_engine_core::Error; +use d_engine_core::FlushPolicy; +use d_engine_core::HardState; +use d_engine_core::LogStore; +use d_engine_core::MetaStore; +use d_engine_core::NetworkError; +use d_engine_core::PersistenceConfig; +use d_engine_core::PersistenceStrategy; +use d_engine_core::RaftLog; +use d_engine_core::Result; +use d_engine_core::StorageEngine; +use d_engine_core::TypeConfig; +use d_engine_core::alias::SOF; use d_engine_proto::common::Entry; use d_engine_proto::common::LogId; @@ -61,7 +61,7 @@ where { sender: Option>>, shutdown: Arc, - pub(crate) worker_handles: Vec>, + pub worker_handles: Vec>, } pub struct FlushTask @@ -108,19 +108,19 @@ where #[allow(dead_code)] node_id: u32, - pub(crate) log_store: Arc< as StorageEngine>::LogStore>, - pub(crate) meta_store: Arc< as StorageEngine>::MetaStore>, + pub log_store: Arc< as StorageEngine>::LogStore>, + pub meta_store: Arc< as StorageEngine>::MetaStore>, pub(crate) strategy: PersistenceStrategy, pub(crate) flush_policy: FlushPolicy, // --- In-memory state --- // Pending entries - pub(crate) entries: SkipMap, + pub entries: SkipMap, // Tracks the highest index that has been persisted to disk - pub(crate) durable_index: AtomicU64, + pub durable_index: AtomicU64, // The next index to be allocated - pub(crate) next_id: AtomicU64, + pub next_id: AtomicU64, // --- In-memory index --- min_index: AtomicU64, // Smallest log index (0 if empty) @@ -134,10 +134,10 @@ where pub(crate) command_sender: mpsc::UnboundedSender, // Track flush state pub(crate) flush_state: Mutex, - pub(crate) waiters: DashMap>>, + pub waiters: DashMap>>, // --- Flush worker pool --- - pub(crate) flush_workers: FlushWorkerPool, + pub flush_workers: FlushWorkerPool, } #[async_trait] @@ -851,7 +851,7 @@ where /// Process entries for flush operation /// Separated to make error handling clearer - pub(super) async fn process_flush( + pub async fn process_flush( &self, indexes: &[u64], ) -> Result<()> { @@ -950,7 +950,7 @@ where /// Efficient range removal with targeted term index updates /// O(k + t) where k = number of entries removed, t = number of affected terms - pub(crate) fn remove_range( + pub fn remove_range( &self, range: RangeInclusive, ) { @@ -1183,8 +1183,8 @@ where debug!("Flush worker pool shut down successfully"); } - #[cfg(test)] - pub(crate) fn len(&self) -> usize { + #[cfg(any(test, feature = "test-utils"))] + pub fn len(&self) -> usize { self.entries.len() } } diff --git a/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs b/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs index 70393c36..6c943e11 100644 --- a/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs +++ b/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs @@ -1,3 +1,5 @@ +use bytes::Bytes; +use futures::future::join_all; use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; @@ -5,9 +7,6 @@ use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::time::Duration; use std::vec; - -use bytes::Bytes; -use futures::future::join_all; use tempfile::tempdir; use tokio::time::Instant; use tokio::time::sleep; @@ -15,35 +14,36 @@ use tracing::debug; use tracing_test::traced_test; use super::*; -// use crate::FileStorageEngine; -use crate::FlushPolicy; -use crate::LogStore; -use crate::MockLogStore; -use crate::MockMetaStore; -use crate::MockStateMachine; -use crate::PersistenceConfig; -use crate::PersistenceStrategy; -use crate::RaftLog; -// use crate::RaftTypeConfig; -use crate::StorageEngine; -use crate::alias::ROF; -use crate::test_utils::MockStorageEngine; -use crate::test_utils::MockTypeConfig; +use crate::BufferedRaftLog; +use crate::FileStorageEngine; +use crate::RaftTypeConfig; use crate::test_utils::generate_insert_commands; use crate::test_utils::{self}; +use d_engine_core::FlushPolicy; +use d_engine_core::LogStore; +use d_engine_core::MockLogStore; +use d_engine_core::MockMetaStore; +use d_engine_core::MockStateMachine; +use d_engine_core::MockStorageEngine; +use d_engine_core::MockTypeConfig; +use d_engine_core::PersistenceConfig; +use d_engine_core::PersistenceStrategy; +use d_engine_core::RaftLog; +use d_engine_core::StorageEngine; +use d_engine_core::alias::ROF; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; use d_engine_proto::common::LogId; // Test utilities struct TestContext { - raft_log: Arc>, - storage: Arc, + raft_log: Arc>>, + storage: Arc, _temp_dir: Option, strategy: PersistenceStrategy, flush_policy: FlushPolicy, - // Instance ID for persistence simulation across "crashes" - instance_id: String, + // Add instance ID to ensure proper crash recovery simulation + path: String, } impl TestContext { @@ -53,11 +53,11 @@ impl TestContext { instance_id: &str, ) -> Self { let temp_dir = tempdir().unwrap(); - let instance_id_owned = instance_id.to_string(); - - let storage = Arc::new(MockStorageEngine::with_id(instance_id_owned.clone())); + let path = temp_dir.path().to_path_buf().join(instance_id); + // let instance_id = instance_id.to_string(); + let storage = Arc::new(FileStorageEngine::new(path.clone()).unwrap()); - let (raft_log, receiver) = BufferedRaftLog::::new( + let (raft_log, receiver) = BufferedRaftLog::new( 1, PersistenceConfig { strategy: strategy.clone(), @@ -73,7 +73,7 @@ impl TestContext { std::thread::sleep(Duration::from_millis(10)); Self { - instance_id: instance_id_owned, + path: path.to_str().unwrap().to_string(), raft_log, storage, strategy, @@ -89,7 +89,7 @@ impl TestContext { // Use the same instance ID to simulate recovery from the same storage let storage = Arc::new(FileStorageEngine::new(PathBuf::from(self.path.clone())).unwrap()); - let (raft_log, receiver) = BufferedRaftLog::::new( + let (raft_log, receiver) = BufferedRaftLog::new( 1, PersistenceConfig { strategy: self.strategy.clone(), @@ -2081,8 +2081,8 @@ mod disk_first_tests { mod mem_first_tests { use super::*; - use crate::LogStore; - use crate::StorageEngine; + use d_engine_core::LogStore; + use d_engine_core::StorageEngine; #[tokio::test] async fn test_basic_write_before_persist() { @@ -2538,9 +2538,9 @@ mod performance_tests { use tokio::time::Duration; use super::*; - use crate::MockLogStore; - use crate::MockMetaStore; - use crate::test_utils::MockStorageEngine; + use d_engine_core::MockLogStore; + use d_engine_core::MockMetaStore; + use d_engine_core::MockStorageEngine; // Test helper: Creates storage with controllable delay fn create_delayed_storage(delay_ms: u64) -> Arc { @@ -2953,10 +2953,10 @@ mod batch_processor_tests { mod save_load_hard_state_tests { use super::*; use crate::FileStorageEngine; - use crate::HardState; - use crate::LogStore; - use crate::MetaStore; - use crate::StorageEngine; + use d_engine_core::HardState; + use d_engine_core::LogStore; + use d_engine_core::MetaStore; + use d_engine_core::StorageEngine; use d_engine_proto::server::election::VotedFor; /// Test that hard state operations use the meta tree and not the log tree @@ -4511,7 +4511,7 @@ mod durable_index_test { use tempfile::tempdir; use tokio::sync::oneshot; - use crate::{FlushPolicy, PersistenceConfig, PersistenceStrategy}; + use d_engine_core::{FlushPolicy, PersistenceConfig, PersistenceStrategy}; use super::*; use std::sync::atomic::Ordering; diff --git a/d-engine-runtime/src/storage/mod.rs b/d-engine-runtime/src/storage/mod.rs index 0639f62f..23152f32 100644 --- a/d-engine-runtime/src/storage/mod.rs +++ b/d-engine-runtime/src/storage/mod.rs @@ -16,4 +16,4 @@ mod adaptors; mod buffered; pub use adaptors::*; -pub(crate) use buffered::*; +pub use buffered::*; diff --git a/d-engine-runtime/src/test_utils/integration/mod.rs b/d-engine-runtime/src/test_utils/integration/mod.rs index 92b500de..f884f867 100644 --- a/d-engine-runtime/src/test_utils/integration/mod.rs +++ b/d-engine-runtime/src/test_utils/integration/mod.rs @@ -55,7 +55,6 @@ use d_engine_core::PersistenceConfig; use d_engine_core::PersistenceStrategy; use d_engine_core::RaftLog; use d_engine_core::RaftNodeConfig; -use d_engine_core::RaftRole; use d_engine_core::ReplicationHandler; use d_engine_core::StateMachine; use d_engine_core::StorageEngine; @@ -65,12 +64,13 @@ use d_engine_core::alias::ROF; use d_engine_core::alias::SMOF; use d_engine_core::alias::TROF; use d_engine_core::convert::safe_kv_bytes; -use d_engine_core::test_utils::MockTypeConfig; use d_engine_proto::client::WriteCommand; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::NodeRole::Follower; use d_engine_proto::common::NodeStatus; use d_engine_proto::server::cluster::NodeMeta; +use prost::Message; use std::ops::RangeInclusive; use std::path::PathBuf; use std::sync::Arc; @@ -143,7 +143,7 @@ pub fn setup_raft_components( let peers_meta = if let Some(meta) = peers_meta_option { meta } else { - let follower_role = RaftRole::::follower_role_i32(); + let follower_role = Follower.into(); vec![ NodeMeta { id: 1, diff --git a/d-engine-runtime/src/test_utils/mock/mock_node_builder.rs b/d-engine-runtime/src/test_utils/mock/mock_node_builder.rs index 8f4c8e17..9cfb9fe8 100644 --- a/d-engine-runtime/src/test_utils/mock/mock_node_builder.rs +++ b/d-engine-runtime/src/test_utils/mock/mock_node_builder.rs @@ -1,39 +1,39 @@ use std::path::Path; -use std::sync::atomic::AtomicBool; use std::sync::Arc; +use std::sync::atomic::AtomicBool; use bytes::Bytes; +use tokio::sync::Mutex; use tokio::sync::mpsc; use tokio::sync::watch; -use tokio::sync::Mutex; use tracing::error; use tracing::trace; use super::MockTypeConfig; -use crate::follower_state::FollowerState; -use crate::grpc; -use crate::ElectionConfig; -use crate::MockElectionCore; -use crate::MockMembership; -use crate::MockPurgeExecutor; -use crate::MockRaftLog; -use crate::MockReplicationCore; -use crate::MockStateMachine; -use crate::MockStateMachineHandler; -use crate::MockTransport; use crate::Node; -use crate::Raft; -use crate::RaftConfig; -use crate::RaftContext; -use crate::RaftCoreHandlers; -use crate::RaftEvent; -use crate::RaftLog; -use crate::RaftNodeConfig; -use crate::RaftRole; -use crate::RaftStorageHandles; -use crate::RoleEvent; -use crate::SignalParams; -use crate::StateMachine; +use crate::grpc; +use d_engine_core::ElectionConfig; +use d_engine_core::MockElectionCore; +use d_engine_core::MockMembership; +use d_engine_core::MockPurgeExecutor; +use d_engine_core::MockRaftLog; +use d_engine_core::MockReplicationCore; +use d_engine_core::MockStateMachine; +use d_engine_core::MockStateMachineHandler; +use d_engine_core::MockTransport; +use d_engine_core::Raft; +use d_engine_core::RaftConfig; +use d_engine_core::RaftContext; +use d_engine_core::RaftCoreHandlers; +use d_engine_core::RaftEvent; +use d_engine_core::RaftLog; +use d_engine_core::RaftNodeConfig; +use d_engine_core::RaftRole; +use d_engine_core::RaftStorageHandles; +use d_engine_core::RoleEvent; +use d_engine_core::SignalParams; +use d_engine_core::StateMachine; +use d_engine_core::follower_state::FollowerState; use d_engine_proto::common::LogId; use d_engine_proto::server::cluster::ClusterMembership; @@ -83,7 +83,7 @@ impl MockBuilder { } } - pub(crate) fn build_context(self) -> RaftContext { + pub fn build_context(self) -> RaftContext { let ( raft_log, state_machine, diff --git a/d-engine-runtime/src/test_utils/mock/mock_rpc_services.rs b/d-engine-runtime/src/test_utils/mock/mock_rpc_services.rs index e164974b..e3783b88 100644 --- a/d-engine-runtime/src/test_utils/mock/mock_rpc_services.rs +++ b/d-engine-runtime/src/test_utils/mock/mock_rpc_services.rs @@ -1,45 +1,258 @@ +use d_engine_core::Result; use d_engine_core::StorageError; +use d_engine_proto::client::ClientReadRequest; use d_engine_proto::client::ClientResponse; +use d_engine_proto::client::ClientWriteRequest; +use d_engine_proto::client::raft_client_service_server::RaftClientService; use d_engine_proto::client::raft_client_service_server::RaftClientServiceServer; +use d_engine_proto::common::NodeRole::Leader; +use d_engine_proto::common::NodeStatus; +use d_engine_proto::server::cluster::ClusterConfChangeRequest; +use d_engine_proto::server::cluster::JoinRequest; +use d_engine_proto::server::cluster::LeaderDiscoveryRequest; +use d_engine_proto::server::cluster::MetadataRequest; +use d_engine_proto::server::cluster::NodeMeta; +use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementService; use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementServiceServer; use d_engine_proto::server::cluster::{ ClusterConfUpdateResponse, ClusterMembership, JoinResponse, LeaderDiscoveryResponse, }; +use d_engine_proto::server::election::VoteRequest; use d_engine_proto::server::election::VoteResponse; +use d_engine_proto::server::election::raft_election_service_server::RaftElectionService; use d_engine_proto::server::election::raft_election_service_server::RaftElectionServiceServer; +use d_engine_proto::server::replication::AppendEntriesRequest; use d_engine_proto::server::replication::AppendEntriesResponse; +use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationService; use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationServiceServer; +use d_engine_proto::server::storage::PurgeLogRequest; +use d_engine_proto::server::storage::SnapshotAck; +use d_engine_proto::server::storage::snapshot_service_server::SnapshotService; use d_engine_proto::server::storage::snapshot_service_server::SnapshotServiceServer; use d_engine_proto::server::storage::{PurgeLogResponse, SnapshotChunk, SnapshotResponse}; use std::net::SocketAddr; use std::sync::Arc; use tokio::net::TcpListener; use tokio::sync::oneshot; +use tokio_stream::StreamExt; +use tonic::Status; +use tonic::Streaming; use tonic::codec::CompressionEncoding; use tonic::transport::Channel; use tonic_health::server::health_reporter; +use tracing::trace; use tracing::{debug, info}; +use crate::test_utils::crate_test_snapshot_stream; + #[derive(Clone, Default)] pub struct MockRpcService { pub server_port: Option, // Expected responses for each method - pub expected_vote_response: Option>, - pub expected_append_entries_response: Option>, + pub expected_vote_response: Option>, + pub expected_append_entries_response: + Option>, pub expected_update_cluster_conf_response: - Option>, - pub expected_client_propose_response: Option>, - pub expected_client_read_response: Option>, + Option>, + pub expected_client_propose_response: + Option>, + pub expected_client_read_response: Option>, #[allow(clippy::type_complexity)] - pub expected_metadata_response: - Option Result + Send + Sync>>, - - pub expected_snapshot_response: Option>, - pub expected_stream_snapshot_response: Option>, - pub expected_purge_log_response: Option>, - pub expected_join_cluster_response: Option>, - pub expected_discover_leader_response: Option>, + pub expected_metadata_response: Option< + Arc std::result::Result + Send + Sync>, + >, + + pub expected_snapshot_response: Option>, + pub expected_stream_snapshot_response: + Option>, + pub expected_purge_log_response: Option>, + pub expected_join_cluster_response: Option>, + pub expected_discover_leader_response: + Option>, +} + +#[tonic::async_trait] +impl RaftElectionService for MockRpcService { + async fn request_vote( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match &self.expected_vote_response { + Some(Ok(response)) => Ok(tonic::Response::new(*response)), + Some(Err(status)) => Err(status.clone()), + None => Err(tonic::Status::unknown("No mock vote response set")), + } + } +} + +#[tonic::async_trait] +impl RaftReplicationService for MockRpcService { + async fn append_entries( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match &self.expected_append_entries_response { + Some(Ok(response)) => Ok(tonic::Response::new(*response)), + Some(Err(status)) => Err(status.clone()), + None => Err(tonic::Status::unknown( + "No mock append entries response set", + )), + } + } +} + +#[tonic::async_trait] +impl ClusterManagementService for MockRpcService { + async fn update_cluster_conf( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match &self.expected_update_cluster_conf_response { + Some(Ok(response)) => Ok(tonic::Response::new(*response)), + Some(Err(status)) => Err(status.clone()), + None => Err(tonic::Status::unknown( + "No mock update_cluster_conf response set", + )), + } + } + async fn get_cluster_metadata( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match (&self.expected_metadata_response, self.server_port) { + (Some(f), Some(port)) => f(port).map(tonic::Response::new).map_err(|e| e.clone()), + _ => Err(tonic::Status::unimplemented( + "Metadata response not configured", + )), + } + } + + async fn join_cluster( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match &self.expected_join_cluster_response { + Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), + Some(Err(status)) => Err(status.clone()), + None => Err(tonic::Status::unknown( + "No mock get_cluster_metadata response set", + )), + } + } + + async fn discover_leader( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match &self.expected_discover_leader_response { + Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), + Some(Err(status)) => Err(status.clone()), + None => Err(tonic::Status::unknown( + "No mock get_cluster_metadata response set", + )), + } + } +} + +#[tonic::async_trait] +impl RaftClientService for MockRpcService { + async fn handle_client_write( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match &self.expected_client_propose_response { + Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), + Some(Err(status)) => Err(status.clone()), + None => Err(tonic::Status::unknown( + "No mock handle_client_write response set", + )), + } + } + + async fn handle_client_read( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match &self.expected_client_read_response { + Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), + Some(Err(status)) => Err(status.clone()), + None => Err(tonic::Status::unknown( + "No mock handle_client_read response set", + )), + } + } +} + +#[tonic::async_trait] +impl SnapshotService for MockRpcService { + type StreamSnapshotStream = tonic::Streaming; + + async fn stream_snapshot( + &self, + _request: tonic::Request>, + ) -> std::result::Result, tonic::Status> { + match &self.expected_stream_snapshot_response { + Some(Ok(response)) => { + let streaming: Self::StreamSnapshotStream = + crate_test_snapshot_stream(vec![response.clone()]); + Ok(tonic::Response::new(streaming)) + } + Some(Err(status)) => Err(status.clone()), + None => Err(tonic::Status::unknown( + "No mock install_snapshot response set", + )), + } + } + async fn install_snapshot( + &self, + request: tonic::Request>, + ) -> std::result::Result, tonic::Status> { + let mut stream = request.into_inner(); + + while let Some(_chunk) = stream.next().await { + trace!("install_snapshot receive chunk - "); + } + trace!("install_snapshot no more to receive!"); + + match &self.expected_snapshot_response { + Some(Ok(response)) => return Ok(tonic::Response::new(*response)), + Some(Err(status)) => return Err(status.clone()), + None => { + return Err(tonic::Status::unknown( + "No mock install_snapshot response set", + )); + } + } + } + + async fn purge_log( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + match &self.expected_purge_log_response { + Some(Ok(response)) => Ok(tonic::Response::new(*response)), + Some(Err(status)) => Err(status.clone()), + None => Err(tonic::Status::unknown("No mock purge_log response set")), + } + } +} + +impl MockRpcService { + pub fn with_metadata_response( + mut self, + f: impl Fn(u16) -> std::result::Result + Send + Sync + 'static, + ) -> Self { + self.expected_metadata_response = Some(Arc::new(f)); + self + } + + pub fn set_port( + &mut self, + port: u16, + ) { + self.server_port = Some(port); + } } pub struct MockNode; @@ -137,4 +350,184 @@ impl MockNode { pub(crate) fn tcp_addr_to_http_addr(addr: String) -> String { format!("http://{addr}") } + + pub(crate) async fn simulate_send_votes_mock_server( + response: VoteResponse, + rx: oneshot::Receiver<()>, + ) -> Result<(Channel, u16)> { + // Return channel + port + //prepare learner's channel address inside membership config + let mock_service = MockRpcService { + expected_vote_response: Some(Ok(response)), + ..Default::default() + }; + let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; + let channel = Self::mock_channel_with_port(port).await; + Ok((channel, port)) + } + + pub(crate) async fn simulate_purge_mock_server( + response: PurgeLogResponse, + rx: oneshot::Receiver<()>, + ) -> Result<(Channel, u16)> { + // Return channel + port + //prepare learner's channel address inside membership config + let mock_service = MockRpcService { + expected_purge_log_response: Some(Ok(response)), + ..Default::default() + }; + let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; + let channel = Self::mock_channel_with_port(port).await; + Ok((channel, port)) + } + + // Return channel + port + pub(crate) async fn simulate_snapshot_mock_server( + response: std::result::Result, + rx: oneshot::Receiver<()>, + ) -> Result<(Channel, u16)> { + //prepare learner's channel address inside membership config + let mock_service = MockRpcService { + expected_snapshot_response: Some(response), + ..Default::default() + }; + let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; + let channel = Self::mock_channel_with_port(port).await; + Ok((channel, port)) + } + + #[allow(clippy::type_complexity)] + pub(crate) async fn simulate_mock_service_with_cluster_conf_reps( + rx: oneshot::Receiver<()>, + response_builder: Option< + Box std::result::Result + Send + Sync>, + >, + ) -> Result<(Channel, u16)> { + let builder = response_builder.unwrap_or_else(|| { + Box::new(|port: u16| { + Ok(ClusterMembership { + version: 1, + nodes: vec![NodeMeta { + id: 1, + role: Leader.into(), + address: format!("127.0.0.1:{port}",), + status: NodeStatus::Active.into(), + }], + }) + }) + }); + + let mock_service = MockRpcService::default().with_metadata_response(builder); + + let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; + let channel = Self::mock_channel_with_port(port).await; + Ok((channel, port)) + } + + pub(crate) async fn simulate_append_entries_mock_server( + response: std::result::Result, + rx: oneshot::Receiver<()>, + ) -> Result<(Channel, u16)> { + //prepare learner's channel address inside membership config + let mock_service = MockRpcService { + expected_append_entries_response: Some(response), + ..Default::default() + }; + let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; + let channel = Self::mock_channel_with_port(port).await; + Ok((channel, port)) + } + + #[allow(clippy::type_complexity)] + pub(crate) async fn simulate_mock_service_with_join_cluster_reps( + rx: oneshot::Receiver<()>, + metadata_response_builder: Option< + Box std::result::Result + Send + Sync>, + >, + join_response: std::result::Result, + ) -> Result<(Channel, u16)> { + let builder = metadata_response_builder.unwrap_or_else(|| { + Box::new(|port: u16| { + Ok(ClusterMembership { + version: 1, + nodes: vec![NodeMeta { + id: 1, + role: Leader.into(), + address: format!("127.0.0.1:{port}",), + status: NodeStatus::Active.into(), + }], + }) + }) + }); + let mock_service = MockRpcService { + expected_metadata_response: Some(Arc::new(builder)), + expected_join_cluster_response: Some(join_response), + ..Default::default() + }; + let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; + let channel = Self::mock_channel_with_port(port).await; + Ok((channel, port)) + } + + #[allow(clippy::type_complexity)] + pub(crate) async fn simulate_client_read_mock_server( + rx: oneshot::Receiver<()>, + metadata_response_builder: Option< + Box std::result::Result + Send + Sync>, + >, + response: ClientResponse, + ) -> Result<(Channel, u16)> { + let builder = metadata_response_builder.unwrap_or_else(|| { + Box::new(|port: u16| { + Ok(ClusterMembership { + version: 1, + nodes: vec![NodeMeta { + id: 1, + role: Leader.into(), + address: format!("127.0.0.1:{port}",), + status: NodeStatus::Active.into(), + }], + }) + }) + }); + let mock_service = MockRpcService { + expected_metadata_response: Some(Arc::new(builder)), + expected_client_read_response: Some(Ok(response)), + ..Default::default() + }; + let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; + let channel = Self::mock_channel_with_port(port).await; + Ok((channel, port)) + } + + #[allow(clippy::type_complexity)] + pub(crate) async fn simulate_client_write_mock_server( + rx: oneshot::Receiver<()>, + metadata_response_builder: Option< + Box std::result::Result + Send + Sync>, + >, + response: ClientResponse, + ) -> Result<(Channel, u16)> { + let builder = metadata_response_builder.unwrap_or_else(|| { + Box::new(|port: u16| { + Ok(ClusterMembership { + version: 1, + nodes: vec![NodeMeta { + id: 1, + role: Leader.into(), + address: format!("127.0.0.1:{port}",), + status: NodeStatus::Active.into(), + }], + }) + }) + }); + let mock_service = MockRpcService { + expected_metadata_response: Some(Arc::new(builder)), + expected_client_propose_response: Some(Ok(response)), + ..Default::default() + }; + let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; + let channel = Self::mock_channel_with_port(port).await; + Ok((channel, port)) + } } diff --git a/d-engine-runtime/src/test_utils/mock/mod.rs b/d-engine-runtime/src/test_utils/mock/mod.rs index 20eacdb1..bc766892 100644 --- a/d-engine-runtime/src/test_utils/mock/mod.rs +++ b/d-engine-runtime/src/test_utils/mock/mod.rs @@ -5,14 +5,13 @@ pub use mock_node_builder::*; pub use mock_rpc_services::*; use crate::Node; -use d_engine_core::{ - Raft, - test_utils::{MockTypeConfig, node_config}, -}; +use d_engine_core::{MockTypeConfig, Raft}; use d_engine_proto::server::cluster::NodeMeta; use tokio::sync::watch; -pub(crate) fn mock_node( +use super::node_config; + +pub fn mock_node( db_path: &str, shutdown_signal: watch::Receiver<()>, peers_meta_option: Option>, @@ -26,7 +25,7 @@ pub(crate) fn mock_node( MockBuilder::new(shutdown_signal).with_node_config(node_config).build_node() } -pub(crate) fn mock_raft( +pub fn mock_raft( db_path: &str, shutdown_signal: watch::Receiver<()>, peers_meta_option: Option>, diff --git a/d-engine-runtime/src/test_utils/mod.rs b/d-engine-runtime/src/test_utils/mod.rs index e7b56a44..dbc3ab6c 100644 --- a/d-engine-runtime/src/test_utils/mod.rs +++ b/d-engine-runtime/src/test_utils/mod.rs @@ -1,5 +1,7 @@ mod integration; mod mock; +mod snapshot; pub use integration::*; pub use mock::*; +pub use snapshot::*; diff --git a/d-engine-runtime/src/test_utils/snapshot.rs b/d-engine-runtime/src/test_utils/snapshot.rs new file mode 100644 index 00000000..ab087e34 --- /dev/null +++ b/d-engine-runtime/src/test_utils/snapshot.rs @@ -0,0 +1,237 @@ +use std::path::Path; + +use bytes::BufMut; +use bytes::Bytes; +use bytes::BytesMut; +use crc32fast::Hasher; +use futures::StreamExt; +use futures::TryStreamExt; +use futures::stream; +use futures::stream::BoxStream; +use http_body::Frame; +use http_body_util::BodyExt; +use http_body_util::StreamBody; +use tokio::fs::File; +use tokio::io::AsyncWriteExt; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; +use tonic::Code; +use tonic::Status; +use tracing::debug; + +use d_engine_core::NetworkError; +use d_engine_core::Result; +use d_engine_core::stream::GrpcStreamDecoder; +use d_engine_proto::common::LogId; +use d_engine_proto::server::storage::SnapshotChunk; +use d_engine_proto::server::storage::SnapshotMetadata; + +/// Helper to create a valid snapshot stream +pub(crate) fn create_snapshot_stream( + chunks: usize, + chunk_size: usize, +) -> BoxStream<'static, Result> { + let chunks: Vec = (0..chunks) + .map(|seq| { + let data = vec![seq as u8; chunk_size]; + create_test_chunk( + seq as u32, + &data, + 1, // term + 1, // leader_id + chunks as u32, + ) + }) + .collect(); + + let stream = crate_test_snapshot_stream(chunks); + Box::pin( + stream.map(|item| item.map_err(|s| NetworkError::TonicStatusError(Box::new(s)).into())), + ) +} + +pub fn crate_test_snapshot_stream(chunks: Vec) -> tonic::Streaming +where + T: prost::Message + Default + 'static, +{ + // Convert chunks to encoded byte streams + let byte_stream = stream::iter(chunks.into_iter().map(|chunk| { + let mut buf = Vec::new(); + + chunk + .encode(&mut buf) + .map_err(|e| Status::new(Code::Internal, format!("Encoding failed: {e}")))?; + + // Add Tonic frame header + let mut frame = BytesMut::new(); + frame.put_u8(0); // No compression + debug!("buf.len()={}", buf.len()); + + frame.put_u32(buf.len() as u32); // Message length + frame.extend_from_slice(&buf); + + Ok(frame.freeze()) + })); + + let body = StreamBody::new( + byte_stream + .map_ok(Frame::data) + .map_err(|e: Status| Status::new(Code::Internal, format!("Stream error: {e}"))), + ); + tonic::Streaming::new_request( + GrpcStreamDecoder::::new(), + body.boxed_unsync(), + None, + Some(1024 * 1024 * 1024), + ) +} + +pub async fn create_test_compressed_snapshot() -> (Vec, SnapshotMetadata) { + // Create a temporary directory for our test data + let temp_dir = tempfile::tempdir().unwrap(); + let temp_path = temp_dir.path(); + + // Create metadata + let metadata = SnapshotMetadata { + last_included: Some(LogId { index: 5, term: 1 }), + checksum: Bytes::from(vec![1; 32]), + }; + + // Create test data file + let data_file = temp_path.join("test_data.bin"); + tokio::fs::write(&data_file, b"test snapshot content").await.unwrap(); + + // Create metadata file + let metadata_bytes = bincode::serialize(&metadata).unwrap(); + tokio::fs::write(temp_path.join("metadata.bin"), &metadata_bytes).await.unwrap(); + + // Create compressed file + let compressed_path = temp_path.join("snapshot.tar.gz"); + let file = tokio::fs::File::create(&compressed_path).await.unwrap(); + let gzip_encoder = async_compression::tokio::write::GzipEncoder::new(file); + let mut tar_builder = tokio_tar::Builder::new(gzip_encoder); + + // Add files to tar + tar_builder.append_path_with_name(&data_file, "test_data.bin").await.unwrap(); + tar_builder + .append_path_with_name(temp_path.join("metadata.bin"), "metadata.bin") + .await + .unwrap(); + + // Finish compression + tar_builder.finish().await.unwrap(); + let mut gzip_encoder = tar_builder.into_inner().await.unwrap(); + gzip_encoder.shutdown().await.unwrap(); + + // Read compressed data back + let compressed_data = tokio::fs::read(&compressed_path).await.unwrap(); + + (compressed_data, metadata) +} + +#[allow(unused)] +pub(crate) fn crate_test_snapshot_stream_from_receiver( + receiver: mpsc::Receiver +) -> tonic::Streaming +where + T: prost::Message + Default + 'static, +{ + let byte_stream = ReceiverStream::new(receiver).map(|item| { + let mut buf = Vec::new(); + item.encode(&mut buf) + .map_err(|e| Status::new(Code::Internal, format!("Encoding failed: {e}")))?; + + let mut frame = BytesMut::new(); + frame.put_u8(0); + frame.put_u32(buf.len() as u32); + frame.extend_from_slice(&buf); + Ok(frame.freeze()) + }); + + let body = StreamBody::new( + byte_stream + .map_ok(Frame::data) + .map_err(|e: Status| Status::new(Code::Internal, format!("Stream error: {e}"))), + ); + + tonic::Streaming::new_request( + GrpcStreamDecoder::::new(), + body.boxed_unsync(), + None, + Some(1024 * 1024 * 1024), + ) +} + +/// Helper to create valid test chunk +pub fn create_test_chunk( + seq: u32, + data: &[u8], + leader_term: u64, + leader_id: u32, + total_chunks: u32, +) -> SnapshotChunk { + SnapshotChunk { + leader_term, + leader_id, + seq, + total_chunks, + chunk_checksum: Bytes::from(compute_checksum(data)), + metadata: Some(SnapshotMetadata { + last_included: Some(LogId { + index: 100, + term: leader_term, + }), + checksum: Bytes::new(), + }), + data: Bytes::from(data.to_vec()), + } +} + +/// Helper to compute CRC32 checksum for test data +fn compute_checksum(data: &[u8]) -> Vec { + let mut hasher = Hasher::new(); + hasher.update(data); + hasher.finalize().to_be_bytes().to_vec() +} + +/// Creates a fake compressed snapshot file for testing +#[allow(unused)] +pub async fn create_fake_compressed_snapshot( + path: &Path, + content: &[u8], +) -> std::result::Result<(), Box> { + use async_compression::tokio::write::GzipEncoder; + use tokio::io::AsyncWriteExt; + + let file = File::create(path).await?; + let mut encoder = GzipEncoder::new(file); + encoder.write_all(content).await?; + encoder.shutdown().await?; + Ok(()) +} + +/// Creates a fake compressed snapshot with directory structure +#[allow(unused)] +pub async fn create_fake_dir_compressed_snapshot( + path: &Path, + files: &[(&str, &[u8])], +) -> std::result::Result<(), Box> { + use async_compression::tokio::write::GzipEncoder; + use tokio_tar::Builder; + + let file = File::create(path).await?; + let gzip_encoder = GzipEncoder::new(file); + let mut tar_builder = Builder::new(gzip_encoder); + + let temp_dir = tempfile::tempdir()?; + for (file_name, content) in files { + let file_path = temp_dir.path().join(file_name); + tokio::fs::write(&file_path, content).await?; + tar_builder.append_path(&file_path).await?; + } + + tar_builder.finish().await?; + let mut gzip_encoder = tar_builder.into_inner().await?; + gzip_encoder.shutdown().await?; + Ok(()) +} diff --git a/d-engine-runtime/src/utils/async_task.rs b/d-engine-runtime/src/utils/async_task.rs index 24c7c98d..d2862c95 100644 --- a/d-engine-runtime/src/utils/async_task.rs +++ b/d-engine-runtime/src/utils/async_task.rs @@ -6,7 +6,7 @@ use tracing::debug; use tracing::error; use tracing::warn; -use crate::BackoffPolicy; +use d_engine_core::BackoffPolicy; use d_engine_core::NetworkError; use d_engine_core::Result; diff --git a/d-engine-runtime/src/utils/async_task_test.rs b/d-engine-runtime/src/utils/async_task_test.rs index 92efded4..eda493f4 100644 --- a/d-engine-runtime/src/utils/async_task_test.rs +++ b/d-engine-runtime/src/utils/async_task_test.rs @@ -1,15 +1,13 @@ +use crate::async_task::spawn_task; +use crate::async_task::task_with_timeout_and_exponential_backoff; +use d_engine_core::BackoffPolicy; +use d_engine_core::Error; +use futures::future::join_all; +use std::sync::Arc; use std::sync::atomic::AtomicU32; use std::sync::atomic::Ordering; -use std::sync::Arc; use std::time::Duration; -use futures::future::join_all; - -use crate::async_task::spawn_task; -use crate::async_task::task_with_timeout_and_exponential_backoff; -use crate::BackoffPolicy; -use crate::Error; - #[tokio::test] async fn test_task_with_timeout_and_exponential_backoff_success() { let counter = Arc::new(AtomicU32::new(0)); @@ -22,7 +20,7 @@ async fn test_task_with_timeout_and_exponential_backoff_success() { if current == 0 { Err(Error::Fatal("First attempt fails".to_string())) } else { - Ok::<_, crate::Error>(current) + Ok::<_, d_engine_core::Error>(current) } } }; diff --git a/d-engine-runtime/src/utils/cluster.rs b/d-engine-runtime/src/utils/cluster.rs index 993abce4..ce6615a6 100644 --- a/d-engine-runtime/src/utils/cluster.rs +++ b/d-engine-runtime/src/utils/cluster.rs @@ -6,11 +6,11 @@ use dashmap::DashSet; use metrics::gauge; use tracing::error; -use crate::Candidate.into(); -use crate::Follower.into(); -use crate::Leader.into(); -use crate::Learner.into(); use d_engine_proto::common::Entry; +use d_engine_proto::common::NodeRole::Candidate; +use d_engine_proto::common::NodeRole::Follower; +use d_engine_proto::common::NodeRole::Leader; +use d_engine_proto::common::NodeRole::Learner; pub(crate) fn collect_ids(entries: &[Entry]) -> Vec { entries.iter().map(|e| e.index).collect() diff --git a/d-engine-runtime/src/utils/mod.rs b/d-engine-runtime/src/utils/mod.rs index fe96667a..77bae00c 100644 --- a/d-engine-runtime/src/utils/mod.rs +++ b/d-engine-runtime/src/utils/mod.rs @@ -4,12 +4,6 @@ pub mod async_task; #[allow(dead_code)] pub mod cluster; -#[allow(dead_code)] -pub mod convert; - -#[allow(dead_code)] -pub mod file_io; - #[allow(dead_code)] pub mod net; @@ -19,14 +13,8 @@ pub mod scoped_timer; #[cfg(test)] mod utils_test; -#[cfg(test)] -mod file_io_test; - #[cfg(test)] mod async_task_test; -#[cfg(test)] -mod convert_test; - #[cfg(test)] mod net_test; diff --git a/d-engine-runtime/src/utils/utils_test.rs b/d-engine-runtime/src/utils/utils_test.rs index 16011dd6..9118fe61 100644 --- a/d-engine-runtime/src/utils/utils_test.rs +++ b/d-engine-runtime/src/utils/utils_test.rs @@ -6,14 +6,14 @@ use tokio::time::sleep; use tracing_test::traced_test; use super::cluster::find_nearest_lower_number; -use crate::BackoffPolicy; use crate::async_task::task_with_timeout_and_exponential_backoff; -use crate::convert::abs_ceil; -use crate::convert::safe_kv; -use crate::convert::safe_vk; use crate::utils::cluster::is_majority; +use d_engine_core::BackoffPolicy; use d_engine_core::NetworkError; use d_engine_core::Result; +use d_engine_core::convert::abs_ceil; +use d_engine_core::convert::safe_kv; +use d_engine_core::convert::safe_vk; #[test] fn test_kv_1() { diff --git a/d-engine-runtime/tests/components/buffered_raft_log_test.rs b/d-engine-runtime/tests/components/buffered_raft_log_test.rs index 1b8f4530..69ac1c4e 100644 --- a/d-engine-runtime/tests/components/buffered_raft_log_test.rs +++ b/d-engine-runtime/tests/components/buffered_raft_log_test.rs @@ -1,3 +1,5 @@ +use bytes::Bytes; +use futures::future::join_all; use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; @@ -5,35 +7,32 @@ use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::time::Duration; use std::vec; - -use bytes::Bytes; -use futures::future::join_all; use tempfile::tempdir; use tokio::time::Instant; use tokio::time::sleep; use tracing::debug; use tracing_test::traced_test; -use super::*; -use crate::FileStorageEngine; -use crate::FlushPolicy; -use crate::LogStore; -use crate::MockLogStore; -use crate::MockMetaStore; -use crate::MockStateMachine; -use crate::PersistenceConfig; -use crate::PersistenceStrategy; -use crate::RaftLog; -use crate::RaftTypeConfig; -use crate::StorageEngine; -use crate::alias::ROF; -use crate::test_utils::MockStorageEngine; -use crate::test_utils::MockTypeConfig; -use crate::test_utils::generate_insert_commands; -use crate::test_utils::{self}; +use d_engine_core::FlushPolicy; +use d_engine_core::LogStore; +use d_engine_core::MockLogStore; +use d_engine_core::MockMetaStore; +use d_engine_core::MockStateMachine; +use d_engine_core::MockStorageEngine; +use d_engine_core::MockTypeConfig; +use d_engine_core::PersistenceConfig; +use d_engine_core::PersistenceStrategy; +use d_engine_core::RaftLog; +use d_engine_core::StorageEngine; +use d_engine_core::alias::ROF; +use d_engine_core::test_utils::generate_insert_commands; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; use d_engine_proto::common::LogId; +use d_engine_runtime::BufferedRaftLog; +use d_engine_runtime::FileStorageEngine; +use d_engine_runtime::RaftTypeConfig; +use d_engine_runtime::test_utils; // Test utilities struct TestContext { @@ -1607,7 +1606,7 @@ mod id_allocation_tests { use std::sync::atomic::Ordering; use super::*; - use crate::FileStorageEngine; + use d_engine_runtime::FileStorageEngine; // In-memory test setup fn setup_memory() -> Arc>> { @@ -2081,8 +2080,8 @@ mod disk_first_tests { mod mem_first_tests { use super::*; - use crate::LogStore; - use crate::StorageEngine; + use d_engine_core::LogStore; + use d_engine_core::StorageEngine; #[tokio::test] async fn test_basic_write_before_persist() { @@ -2377,7 +2376,7 @@ mod common_tests { mod filter_out_conflicts_and_append_performance_tests { use super::*; - use crate::FileStorageEngine; + use d_engine_runtime::FileStorageEngine; #[tokio::test] async fn test_filter_out_conflicts_performance_consistent_across_flush_intervals_fresh_cluster() @@ -2533,14 +2532,13 @@ mod filter_out_conflicts_and_append_performance_tests { mod performance_tests { use std::sync::Arc; - use tokio::sync::Barrier; use tokio::time::Duration; use super::*; - use crate::MockLogStore; - use crate::MockMetaStore; - use crate::test_utils::MockStorageEngine; + use d_engine_core::MockLogStore; + use d_engine_core::MockMetaStore; + use d_engine_core::MockStorageEngine; // Test helper: Creates storage with controllable delay fn create_delayed_storage(delay_ms: u64) -> Arc { @@ -2815,11 +2813,11 @@ mod performance_tests { } mod batch_processor_tests { + use d_engine_runtime::LogCommand; use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::time::Duration; - use tokio::sync::Notify; use tokio::sync::mpsc; use tokio::sync::oneshot; @@ -2952,12 +2950,12 @@ mod batch_processor_tests { mod save_load_hard_state_tests { use super::*; - use crate::FileStorageEngine; - use crate::HardState; - use crate::LogStore; - use crate::MetaStore; - use crate::StorageEngine; + use d_engine_core::HardState; + use d_engine_core::LogStore; + use d_engine_core::MetaStore; + use d_engine_core::StorageEngine; use d_engine_proto::server::election::VotedFor; + use d_engine_runtime::FileStorageEngine; /// Test that hard state operations use the meta tree and not the log tree #[tokio::test] @@ -3213,7 +3211,7 @@ async fn test_last_entry_id_performance() { #[cfg(test)] mod remove_range_tests { use super::*; - use crate::test_utils; + use d_engine_runtime::test_utils; #[tokio::test] async fn test_remove_middle_range() { @@ -4511,7 +4509,7 @@ mod durable_index_test { use tempfile::tempdir; use tokio::sync::oneshot; - use crate::{FlushPolicy, PersistenceConfig, PersistenceStrategy}; + use d_engine_core::{FlushPolicy, PersistenceConfig, PersistenceStrategy}; use super::*; use std::sync::atomic::Ordering; diff --git a/d-engine-runtime/tests/components/election/election_handler_test.rs b/d-engine-runtime/tests/components/election/election_handler_test.rs index 79876feb..20f11936 100644 --- a/d-engine-runtime/tests/components/election/election_handler_test.rs +++ b/d-engine-runtime/tests/components/election/election_handler_test.rs @@ -1,23 +1,23 @@ -use std::sync::Arc; +use d_engine_runtime::test_utils::*; +use std::sync::Arc; use tokio::sync::watch; use tracing::debug; use tracing_test::traced_test; -use crate::ConsensusError; -use crate::ElectionCore; -use crate::ElectionError; -use crate::ElectionHandler; -use crate::Error; -use crate::MockMembership; -use crate::MockRaftLog; -use crate::MockTransport; -use crate::VoteResult; -use crate::alias::ROF; -use crate::alias::TROF; -use crate::test_utils::MockTypeConfig; -use crate::test_utils::mock_membership; -use crate::test_utils::setup_raft_components; +use d_engine_core::ConsensusError; +use d_engine_core::ElectionCore; +use d_engine_core::ElectionError; +use d_engine_core::ElectionHandler; +use d_engine_core::Error; +use d_engine_core::MockMembership; +use d_engine_core::MockRaftLog; +use d_engine_core::MockTransport; +use d_engine_core::MockTypeConfig; +use d_engine_core::VoteResult; +use d_engine_core::alias::ROF; +use d_engine_core::alias::TROF; +use d_engine_core::mock_membership; use d_engine_proto::common::LogId; use d_engine_proto::common::NodeRole::Follower; use d_engine_proto::common::NodeStatus; diff --git a/d-engine-runtime/tests/components/election/mod.rs b/d-engine-runtime/tests/components/election/mod.rs index 6a01979b..d32761dd 100644 --- a/d-engine-runtime/tests/components/election/mod.rs +++ b/d-engine-runtime/tests/components/election/mod.rs @@ -1 +1,2 @@ +#[cfg(test)] mod election_handler_test; diff --git a/d-engine-runtime/tests/components/mod.rs b/d-engine-runtime/tests/components/mod.rs index f27ea6b4..d7ec697d 100644 --- a/d-engine-runtime/tests/components/mod.rs +++ b/d-engine-runtime/tests/components/mod.rs @@ -1,4 +1,5 @@ mod buffered_raft_log_test; mod election; mod raft_role; +mod raft_test; mod replication; diff --git a/d-engine-runtime/tests/components/raft_role/candidate_state_test.rs b/d-engine-runtime/tests/components/raft_role/candidate_state_test.rs index fb57283f..ec6ed885 100644 --- a/d-engine-runtime/tests/components/raft_role/candidate_state_test.rs +++ b/d-engine-runtime/tests/components/raft_role/candidate_state_test.rs @@ -1,32 +1,30 @@ -use std::sync::Arc; - use bytes::Bytes; +use std::sync::Arc; use tokio::sync::mpsc; use tokio::sync::watch; use tonic::Code; use tonic::Status; use tracing_test::traced_test; -use super::candidate_state::CandidateState; -use crate::ConsensusError; -use crate::ElectionError; -use crate::Error; -use crate::MaybeCloneOneshot; -use crate::MaybeCloneOneshotSender; -use crate::MockElectionCore; -use crate::MockMembership; -use crate::MockReplicationCore; -use crate::MockStateMachineHandler; -use crate::RaftEvent; -use crate::RaftOneshot; -use crate::RoleEvent; -use crate::role_state::RaftRoleState; -use crate::test_utils::MockTypeConfig; -use crate::test_utils::crate_test_snapshot_stream; -use crate::test_utils::create_test_chunk; -use crate::test_utils::mock_election_core; -use crate::test_utils::mock_raft_context; -use crate::test_utils::setup_raft_components; +use d_engine_core::ConsensusError; +use d_engine_core::ElectionError; +use d_engine_core::Error; +use d_engine_core::MaybeCloneOneshot; +use d_engine_core::MaybeCloneOneshotSender; +use d_engine_core::MockElectionCore; +use d_engine_core::MockMembership; +use d_engine_core::MockReplicationCore; +use d_engine_core::MockStateMachineHandler; +use d_engine_core::MockTypeConfig; +use d_engine_core::RaftEvent; +use d_engine_core::RaftOneshot; +use d_engine_core::RoleEvent; +use d_engine_core::candidate_state::CandidateState; +use d_engine_core::role_state::RaftRoleState; +use d_engine_core::test_utils::crate_test_snapshot_stream; +use d_engine_core::test_utils::create_test_chunk; +use d_engine_core::test_utils::mock_election_core; +use d_engine_core::test_utils::mock_raft_context; use d_engine_proto::client::ClientReadRequest; use d_engine_proto::client::ClientWriteRequest; use d_engine_proto::client::ReadConsistencyPolicy; @@ -46,6 +44,7 @@ use d_engine_proto::server::election::VotedFor; use d_engine_proto::server::replication::AppendEntriesRequest; use d_engine_proto::server::replication::AppendEntriesResponse; use d_engine_proto::server::storage::PurgeLogRequest; +use d_engine_runtime::test_utils::*; /// # Case 1: Can vote myself #[tokio::test] @@ -146,7 +145,7 @@ fn setup_handle_raft_event_case1_params( resp_tx: MaybeCloneOneshotSender>, term: u64, ) -> RaftEvent { - crate::RaftEvent::ReceiveVoteRequest( + d_engine_core::RaftEvent::ReceiveVoteRequest( VoteRequest { term, candidate_id: 1, @@ -263,7 +262,7 @@ async fn test_handle_raft_event_case2() { // Prepare function params let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); let (role_tx, _role_rx) = mpsc::unbounded_channel(); - let raft_event = crate::RaftEvent::ClusterConf(MetadataRequest {}, resp_tx); + let raft_event = d_engine_core::RaftEvent::ClusterConf(MetadataRequest {}, resp_tx); assert!(state.handle_raft_event(raft_event, &context, role_tx).await.is_ok()); @@ -301,7 +300,7 @@ async fn test_handle_raft_event_case3() { // Prepare function params let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); let (role_tx, _role_rx) = mpsc::unbounded_channel(); - let raft_event = crate::RaftEvent::ClusterConfUpdate( + let raft_event = d_engine_core::RaftEvent::ClusterConfUpdate( ClusterConfChangeRequest { id: 2, // Leader ID term: 1, @@ -381,7 +380,7 @@ async fn test_handle_raft_event_case4_1() { leader_commit_index: new_leader_commit, }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); - let raft_event = crate::RaftEvent::AppendEntries(append_entries_request, resp_tx); + let raft_event = d_engine_core::RaftEvent::AppendEntries(append_entries_request, resp_tx); let (role_tx, mut role_rx) = mpsc::unbounded_channel(); @@ -456,7 +455,7 @@ async fn test_handle_raft_event_case4_2() { leader_commit_index: 0, }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); - let raft_event = crate::RaftEvent::AppendEntries(append_entries_request, resp_tx); + let raft_event = d_engine_core::RaftEvent::AppendEntries(append_entries_request, resp_tx); let (role_tx, mut role_rx) = mpsc::unbounded_channel(); @@ -522,7 +521,7 @@ async fn test_handle_raft_event_case4_3() { leader_commit_index: 0, }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); - let raft_event = crate::RaftEvent::AppendEntries(append_entries_request, resp_tx); + let raft_event = d_engine_core::RaftEvent::AppendEntries(append_entries_request, resp_tx); let (role_tx, mut role_rx) = mpsc::unbounded_channel(); @@ -555,7 +554,7 @@ async fn test_handle_raft_event_case5() { // Handle raft event let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); - let raft_event = crate::RaftEvent::ClientPropose( + let raft_event = d_engine_core::RaftEvent::ClientPropose( ClientWriteRequest { client_id: 1, commands: vec![], @@ -798,11 +797,11 @@ mod role_violation_tests { #[cfg(test)] mod handle_client_read_request { use super::*; - use crate::RaftNodeConfig; - use crate::config::ReadConsistencyPolicy as ServerPolicy; - use crate::convert::safe_kv_bytes; - use crate::test_utils::MockBuilder; + use d_engine_core::RaftNodeConfig; + use d_engine_core::config::ReadConsistencyPolicy as ServerPolicy; + use d_engine_core::convert::safe_kv_bytes; use d_engine_proto::client::ReadConsistencyPolicy as ClientPolicy; + use d_engine_runtime::test_utils::MockBuilder; /// # Case 6.1: test ClientReadRequest with linear request #[tokio::test] @@ -819,7 +818,7 @@ mod handle_client_read_request { keys: vec![], }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); - let raft_event = crate::RaftEvent::ClientReadRequest(client_read_request, resp_tx); + let raft_event = d_engine_core::RaftEvent::ClientReadRequest(client_read_request, resp_tx); let (role_tx, _role_rx) = mpsc::unbounded_channel(); assert!(state.handle_raft_event(raft_event, &context, role_tx).await.is_ok()); @@ -851,7 +850,7 @@ mod handle_client_read_request { keys: vec![], }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); - let raft_event = crate::RaftEvent::ClientReadRequest(client_read_request, resp_tx); + let raft_event = d_engine_core::RaftEvent::ClientReadRequest(client_read_request, resp_tx); let (role_tx, _role_rx) = mpsc::unbounded_channel(); assert!(state.handle_raft_event(raft_event, &context, role_tx).await.is_ok()); diff --git a/d-engine-runtime/tests/components/raft_role/follower_state_test.rs b/d-engine-runtime/tests/components/raft_role/follower_state_test.rs index ff771084..5ff99401 100644 --- a/d-engine-runtime/tests/components/raft_role/follower_state_test.rs +++ b/d-engine-runtime/tests/components/raft_role/follower_state_test.rs @@ -1,6 +1,7 @@ -use std::sync::Arc; +use d_engine_runtime::test_utils::*; use bytes::Bytes; +use std::sync::Arc; use tempfile::tempdir; use tokio::sync::mpsc; use tokio::sync::watch; @@ -8,33 +9,32 @@ use tonic::Code; use tonic::Status; use tracing_test::traced_test; -use super::HardState; -use super::follower_state::FollowerState; -use crate::AppendResponseWithUpdates; -use crate::ConsensusError; -use crate::Error; -use crate::MaybeCloneOneshot; -use crate::MaybeCloneOneshotSender; -use crate::MembershipError; -use crate::MockElectionCore; -use crate::MockMembership; -use crate::MockPurgeExecutor; -use crate::MockReplicationCore; -use crate::MockStateMachineHandler; -use crate::NetworkError; -use crate::NewCommitData; -use crate::RaftEvent; -use crate::RaftOneshot; -use crate::RoleEvent; -use crate::SnapshotError; -use crate::StateUpdate; -use crate::SystemError; -use crate::role_state::RaftRoleState; -use crate::test_utils::MockBuilder; -use crate::test_utils::MockTypeConfig; -use crate::test_utils::mock_raft_context; -use crate::test_utils::node_config; -use crate::test_utils::setup_raft_components; +use d_engine_core::AppendResponseWithUpdates; +use d_engine_core::ConsensusError; +use d_engine_core::Error; +use d_engine_core::HardState; +use d_engine_core::MaybeCloneOneshot; +use d_engine_core::MaybeCloneOneshotSender; +use d_engine_core::MembershipError; +use d_engine_core::MockElectionCore; +use d_engine_core::MockMembership; +use d_engine_core::MockPurgeExecutor; +use d_engine_core::MockReplicationCore; +use d_engine_core::MockStateMachineHandler; +use d_engine_core::MockTypeConfig; +use d_engine_core::NetworkError; +use d_engine_core::NewCommitData; +use d_engine_core::RaftEvent; +use d_engine_core::RaftOneshot; +use d_engine_core::RoleEvent; +use d_engine_core::SnapshotError; +use d_engine_core::StateUpdate; +use d_engine_core::SystemError; +use d_engine_core::follower_state::FollowerState; +use d_engine_core::role_state::RaftRoleState; +use d_engine_core::test_utils::MockBuilder; +use d_engine_core::test_utils::mock_raft_context; +use d_engine_core::test_utils::node_config; use d_engine_proto::client::ClientReadRequest; use d_engine_proto::client::ClientWriteRequest; use d_engine_proto::client::ReadConsistencyPolicy; @@ -178,7 +178,7 @@ async fn test_tick() { fn setup_handle_raft_event_case1_params( resp_tx: MaybeCloneOneshotSender> ) -> RaftEvent { - crate::RaftEvent::ReceiveVoteRequest( + d_engine_core::RaftEvent::ReceiveVoteRequest( VoteRequest { term: 1, candidate_id: 1, @@ -347,7 +347,7 @@ async fn test_handle_raft_event_case2() { // Prepare function params let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); let (role_tx, _role_rx) = mpsc::unbounded_channel(); - let raft_event = crate::RaftEvent::ClusterConf(MetadataRequest {}, resp_tx); + let raft_event = d_engine_core::RaftEvent::ClusterConf(MetadataRequest {}, resp_tx); assert!(state.handle_raft_event(raft_event, &context, role_tx).await.is_ok()); @@ -386,7 +386,7 @@ async fn test_handle_raft_event_case3_1() { // Prepare function params let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); let (role_tx, _role_rx) = mpsc::unbounded_channel(); - let raft_event = crate::RaftEvent::ClusterConfUpdate( + let raft_event = d_engine_core::RaftEvent::ClusterConfUpdate( ClusterConfChangeRequest { id: 2, // Leader ID term: 1, @@ -437,7 +437,7 @@ async fn test_handle_raft_event_case3_2() { // Prepare function params let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); let (role_tx, _role_rx) = mpsc::unbounded_channel(); - let raft_event = crate::RaftEvent::ClusterConfUpdate( + let raft_event = d_engine_core::RaftEvent::ClusterConfUpdate( ClusterConfChangeRequest { id: 3, // Non-leader ID term: 1, @@ -488,7 +488,7 @@ async fn test_handle_raft_event_case3_3() { // Prepare function params let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); let (role_tx, _role_rx) = mpsc::unbounded_channel(); - let raft_event = crate::RaftEvent::ClusterConfUpdate( + let raft_event = d_engine_core::RaftEvent::ClusterConfUpdate( ClusterConfChangeRequest { id: 2, // Leader ID term: 1, @@ -541,7 +541,7 @@ async fn test_handle_raft_event_case3_4() { // Prepare function params let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); let (role_tx, _role_rx) = mpsc::unbounded_channel(); - let raft_event = crate::RaftEvent::ClusterConfUpdate( + let raft_event = d_engine_core::RaftEvent::ClusterConfUpdate( ClusterConfChangeRequest { id: 2, // Leader ID term: 4, // Stale term @@ -589,7 +589,7 @@ async fn test_handle_raft_event_case3_5() { // Prepare function params let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); let (role_tx, _role_rx) = mpsc::unbounded_channel(); - let raft_event = crate::RaftEvent::ClusterConfUpdate( + let raft_event = d_engine_core::RaftEvent::ClusterConfUpdate( ClusterConfChangeRequest { id: 2, // Leader ID term: 1, @@ -640,7 +640,7 @@ async fn test_handle_raft_event_case3_6() { // Prepare function params let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); let (role_tx, _role_rx) = mpsc::unbounded_channel(); - let raft_event = crate::RaftEvent::ClusterConfUpdate( + let raft_event = d_engine_core::RaftEvent::ClusterConfUpdate( ClusterConfChangeRequest { id: 3, // Non-leader ID term: 1, @@ -729,7 +729,7 @@ async fn test_handle_raft_event_case4_1() { leader_commit_index: 0, }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); - let raft_event = crate::RaftEvent::AppendEntries(append_entries_request, resp_tx); + let raft_event = d_engine_core::RaftEvent::AppendEntries(append_entries_request, resp_tx); let (role_tx, mut role_rx) = mpsc::unbounded_channel(); @@ -812,7 +812,7 @@ async fn test_handle_raft_event_case4_2() { leader_commit_index: 0, }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); - let raft_event = crate::RaftEvent::AppendEntries(append_entries_request, resp_tx); + let raft_event = d_engine_core::RaftEvent::AppendEntries(append_entries_request, resp_tx); let (role_tx, mut role_rx) = mpsc::unbounded_channel(); @@ -890,7 +890,7 @@ async fn test_handle_raft_event_case4_3() { leader_commit_index: 0, }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); - let raft_event = crate::RaftEvent::AppendEntries(append_entries_request, resp_tx); + let raft_event = d_engine_core::RaftEvent::AppendEntries(append_entries_request, resp_tx); let (role_tx, mut role_rx) = mpsc::unbounded_channel(); @@ -924,7 +924,7 @@ async fn test_handle_raft_event_case5() { // Handle raft event let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); - let raft_event = crate::RaftEvent::ClientPropose( + let raft_event = d_engine_core::RaftEvent::ClientPropose( ClientWriteRequest { client_id: 1, commands: vec![], @@ -955,7 +955,7 @@ async fn test_handle_raft_event_case6_1() { keys: vec![], }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); - let raft_event = crate::RaftEvent::ClientReadRequest(client_read_request, resp_tx); + let raft_event = d_engine_core::RaftEvent::ClientReadRequest(client_read_request, resp_tx); let (role_tx, _role_rx) = mpsc::unbounded_channel(); assert!(state.handle_raft_event(raft_event, &context, role_tx).await.is_ok()); @@ -987,7 +987,7 @@ async fn test_handle_raft_event_case6_2() { keys: vec![], }; let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); - let raft_event = crate::RaftEvent::ClientReadRequest(client_read_request, resp_tx); + let raft_event = d_engine_core::RaftEvent::ClientReadRequest(client_read_request, resp_tx); let (role_tx, _role_rx) = mpsc::unbounded_channel(); assert!(state.handle_raft_event(raft_event, &context, role_tx).await.is_ok()); @@ -1685,9 +1685,9 @@ mod role_violation_tests { #[cfg(test)] mod handle_client_read_request { use super::*; - use crate::RaftNodeConfig; - use crate::config::ReadConsistencyPolicy as ServerPolicy; - use crate::convert::safe_kv_bytes; + use d_engine_core::RaftNodeConfig; + use d_engine_core::config::ReadConsistencyPolicy as ServerPolicy; + use d_engine_core::convert::safe_kv_bytes; use d_engine_proto::client::ReadConsistencyPolicy as ClientPolicy; /// Test that follower rejects LeaseRead policy diff --git a/d-engine-runtime/tests/components/raft_role/leader_state_test.rs b/d-engine-runtime/tests/components/raft_role/leader_state_test.rs index 1048a6ad..95d1ff5a 100644 --- a/d-engine-runtime/tests/components/raft_role/leader_state_test.rs +++ b/d-engine-runtime/tests/components/raft_role/leader_state_test.rs @@ -1,12 +1,13 @@ -use std::collections::HashMap; -use std::collections::VecDeque; -use std::sync::Arc; -use std::sync::atomic::Ordering; +use d_engine_runtime::test_utils::*; use bytes::Bytes; use futures::StreamExt; use mockall::predicate::eq; use nanoid::nanoid; +use std::collections::HashMap; +use std::collections::VecDeque; +use std::sync::Arc; +use std::sync::atomic::Ordering; use tokio::sync::mpsc; use tokio::sync::watch; use tonic::Code; @@ -14,41 +15,40 @@ use tonic::Status; use tonic::transport::Endpoint; use tracing_test::traced_test; -use super::leader_state::LeaderState; -use super::role_state::RaftRoleState; -use crate::AppendResults; -use crate::ConsensusError; -use crate::Error; -use crate::MaybeCloneOneshot; -use crate::MaybeCloneOneshotReceiver; -use crate::MaybeCloneOneshotSender; -use crate::MockMembership; -use crate::MockPurgeExecutor; -use crate::MockRaftLog; -use crate::MockReplicationCore; -use crate::MockStateMachine; -use crate::MockStateMachineHandler; -use crate::MockTransport; -use crate::NewCommitData; -use crate::PeerUpdate; -use crate::QuorumVerificationResult; -use crate::RaftContext; -use crate::RaftEvent; -use crate::RaftNodeConfig; -use crate::RaftOneshot; -use crate::RaftRequestWithSignal; -use crate::ReplicationError; -use crate::RoleEvent; -use crate::SnapshotError; -use crate::client_command_to_entry_payloads; -use crate::convert::safe_kv_bytes; -use crate::test_utils::MockBuilder; -use crate::test_utils::MockTypeConfig; -use crate::test_utils::crate_test_snapshot_stream; -use crate::test_utils::create_test_chunk; -use crate::test_utils::mock_raft_context; -use crate::test_utils::node_config; -use crate::test_utils::setup_raft_components; +use d_engine_core::AppendResults; +use d_engine_core::ConsensusError; +use d_engine_core::Error; +use d_engine_core::MaybeCloneOneshot; +use d_engine_core::MaybeCloneOneshotReceiver; +use d_engine_core::MaybeCloneOneshotSender; +use d_engine_core::MockMembership; +use d_engine_core::MockPurgeExecutor; +use d_engine_core::MockRaftLog; +use d_engine_core::MockReplicationCore; +use d_engine_core::MockStateMachine; +use d_engine_core::MockStateMachineHandler; +use d_engine_core::MockTransport; +use d_engine_core::MockTypeConfig; +use d_engine_core::NewCommitData; +use d_engine_core::PeerUpdate; +use d_engine_core::QuorumVerificationResult; +use d_engine_core::RaftContext; +use d_engine_core::RaftEvent; +use d_engine_core::RaftNodeConfig; +use d_engine_core::RaftOneshot; +use d_engine_core::RaftRequestWithSignal; +use d_engine_core::ReplicationError; +use d_engine_core::RoleEvent; +use d_engine_core::SnapshotError; +use d_engine_core::client_command_to_entry_payloads; +use d_engine_core::convert::safe_kv_bytes; +use d_engine_core::leader_state::LeaderState; +use d_engine_core::role_state::RaftRoleState; +use d_engine_core::test_utils::MockBuilder; +use d_engine_core::test_utils::crate_test_snapshot_stream; +use d_engine_core::test_utils::create_test_chunk; +use d_engine_core::test_utils::mock_raft_context; +use d_engine_core::test_utils::node_config; use d_engine_proto::client::ClientReadRequest; use d_engine_proto::client::ClientResponse; use d_engine_proto::client::ClientWriteRequest; @@ -3057,13 +3057,12 @@ async fn test_handle_join_cluster_case5_snapshot_triggered() { #[cfg(test)] mod trigger_background_snapshot_test { - use std::sync::Arc; - use futures::stream; + use std::sync::Arc; use super::*; - use crate::SnapshotConfig; - use crate::raft_role::leader_state::LeaderState; + use d_engine_core::SnapshotConfig; + use d_engine_core::leader_state::LeaderState; use d_engine_proto::server::storage::SnapshotChunk; use d_engine_proto::server::storage::SnapshotMetadata; @@ -3083,7 +3082,7 @@ mod trigger_background_snapshot_test { let mut handler = MockStateMachineHandler::::new(); handler.expect_load_snapshot_data().returning(move |_| { if should_fail { - Err(crate::SnapshotError::OperationFailed("mock error".to_string()).into()) + Err(d_engine_core::SnapshotError::OperationFailed("mock error".to_string()).into()) } else { let chunk = SnapshotChunk { data: Bytes::from(vec![1, 2, 3]), @@ -3119,7 +3118,7 @@ mod trigger_background_snapshot_test { let config = default_snapshot_config(); let metadata = make_metadata(); - let result = LeaderState::::trigger_background_snapshot( + let result = LeaderState::::trigger_background_snapshot( 2, metadata, sm_handler, membership, config, ) .await; @@ -3135,7 +3134,7 @@ mod trigger_background_snapshot_test { let config = default_snapshot_config(); let metadata = make_metadata(); - let result = LeaderState::::trigger_background_snapshot( + let result = LeaderState::::trigger_background_snapshot( 2, metadata, sm_handler, membership, config, ) .await; @@ -3151,7 +3150,7 @@ mod trigger_background_snapshot_test { let config = default_snapshot_config(); let metadata = make_metadata(); - let result = LeaderState::::trigger_background_snapshot( + let result = LeaderState::::trigger_background_snapshot( 2, metadata, sm_handler, membership, config, ) .await; @@ -3167,10 +3166,10 @@ mod batch_promote_learners_test { use mockall::predicate::*; use super::*; - use crate::RaftContext; - use crate::membership::MockMembership; - use crate::raft_role::RoleEvent; - use crate::raft_role::leader_state::LeaderState; + use d_engine_core::MockMembership; + use d_engine_core::RaftContext; + use d_engine_core::RoleEvent; + use d_engine_core::leader_state::LeaderState; use d_engine_proto::common::NodeStatus; enum VerifyInternalQuorumWithRetrySuccess { @@ -3370,8 +3369,8 @@ mod pending_promotion_tests { use tokio::time::timeout; use super::*; - use crate::leader_state::PendingPromotion; - use crate::leader_state::calculate_safe_batch_size; + use d_engine_core::leader_state::PendingPromotion; + use d_engine_core::leader_state::calculate_safe_batch_size; // Test fixture struct TestFixture { @@ -3741,14 +3740,12 @@ mod pending_promotion_tests { mod stale_learner_tests { use std::sync::Arc; use std::time::Duration; - use tokio::time::Instant; use super::*; - use crate::RaftNodeConfig; - use crate::leader_state::PendingPromotion; - use crate::test_utils; - use crate::test_utils::*; // Assuming you have test utilities + use d_engine_core::RaftNodeConfig; + use d_engine_core::leader_state::PendingPromotion; + use d_engine_core::test_utils; // Setup helper fn create_test_leader_state( @@ -3947,8 +3944,8 @@ mod stale_learner_tests { #[cfg(test)] mod handle_client_read_request { use super::*; - use crate::config::ReadConsistencyPolicy as ServerPolicy; - use crate::convert::safe_kv_bytes; + use d_engine_core::config::ReadConsistencyPolicy as ServerPolicy; + use d_engine_core::convert::safe_kv_bytes; use d_engine_proto::client::ReadConsistencyPolicy as ClientPolicy; /// Test LeaseRead policy with valid lease @@ -4179,8 +4176,8 @@ mod handle_client_read_request { #[cfg(test)] mod lease_validity_tests { use super::*; - use crate::RaftNodeConfig; - use crate::test_utils::MockBuilder; + use d_engine_core::RaftNodeConfig; + use d_engine_core::test_utils::MockBuilder; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::sync::watch; diff --git a/d-engine-runtime/tests/components/raft_role/learner_state_test.rs b/d-engine-runtime/tests/components/raft_role/learner_state_test.rs index a109894e..6a48f31a 100644 --- a/d-engine-runtime/tests/components/raft_role/learner_state_test.rs +++ b/d-engine-runtime/tests/components/raft_role/learner_state_test.rs @@ -1,34 +1,33 @@ +use bytes::Bytes; use std::sync::Arc; use std::thread::sleep; use std::time::Duration; - -use bytes::Bytes; use tokio::sync::mpsc; use tokio::sync::watch; use tonic::Code; use tracing::debug; use tracing_test::traced_test; -use crate::AppendResponseWithUpdates; -use crate::ConsensusError; -use crate::Error; -use crate::MaybeCloneOneshot; -use crate::MembershipError; -use crate::MockMembership; -use crate::MockReplicationCore; -use crate::MockTransport; -use crate::NetworkError; -use crate::NewCommitData; -use crate::RaftContext; -use crate::RaftEvent; -use crate::RaftOneshot; -use crate::RoleEvent; -use crate::SystemError; -use crate::learner_state::LearnerState; -use crate::role_state::RaftRoleState; -use crate::test_utils::MockTypeConfig; -use crate::test_utils::mock_membership; -use crate::test_utils::mock_raft_context; +use d_engine_core::AppendResponseWithUpdates; +use d_engine_core::ConsensusError; +use d_engine_core::Error; +use d_engine_core::MaybeCloneOneshot; +use d_engine_core::MembershipError; +use d_engine_core::MockMembership; +use d_engine_core::MockReplicationCore; +use d_engine_core::MockTransport; +use d_engine_core::MockTypeConfig; +use d_engine_core::NetworkError; +use d_engine_core::NewCommitData; +use d_engine_core::RaftContext; +use d_engine_core::RaftEvent; +use d_engine_core::RaftOneshot; +use d_engine_core::RoleEvent; +use d_engine_core::SystemError; +use d_engine_core::learner_state::LearnerState; +use d_engine_core::role_state::RaftRoleState; +use d_engine_core::test_utils::mock_membership; +use d_engine_core::test_utils::mock_raft_context; use d_engine_proto::client::ClientReadRequest; use d_engine_proto::client::ClientWriteRequest; use d_engine_proto::common::LogId; @@ -151,7 +150,7 @@ async fn test_handle_raft_event_case3() { // Prepare function params let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); let (role_tx, _role_rx) = mpsc::unbounded_channel(); - let raft_event = crate::RaftEvent::ClusterConfUpdate( + let raft_event = d_engine_core::RaftEvent::ClusterConfUpdate( ClusterConfChangeRequest { id: 2, // Leader ID term: 1, diff --git a/d-engine-runtime/tests/raft_test.rs b/d-engine-runtime/tests/components/raft_test.rs similarity index 97% rename from d-engine-runtime/tests/raft_test.rs rename to d-engine-runtime/tests/components/raft_test.rs index bfc40682..90358543 100644 --- a/d-engine-runtime/tests/raft_test.rs +++ b/d-engine-runtime/tests/components/raft_test.rs @@ -1,34 +1,37 @@ +use d_engine_core::MockReplicationCore; +use d_engine_core::RaftEvent; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; - use tokio::sync::mpsc; use tokio::sync::watch; use tokio::time; use tokio::time::timeout; use tracing_test::traced_test; -use super::*; -use crate::AppendResults; -use crate::ConsensusError; -use crate::ElectionError; -use crate::Error; -use crate::MaybeCloneOneshot; -use crate::MockElectionCore; -use crate::MockMembership; -use crate::MockRaftLog; -use crate::MockTransport; -use crate::PeerUpdate; -use crate::RaftOneshot; -use crate::VoteResult; -use crate::candidate_state::CandidateState; -use crate::cluster::is_candidate; -use crate::cluster::is_follower; -use crate::cluster::is_leader; -use crate::cluster::is_learner; -use crate::leader_state::LeaderState; -use crate::test_utils::MockTypeConfig; -use crate::test_utils::mock_raft; +use d_engine_core::AppendResults; +use d_engine_core::ConsensusError; +use d_engine_core::ElectionError; +use d_engine_core::Error; +use d_engine_core::MaybeCloneOneshot; +use d_engine_core::MockElectionCore; +use d_engine_core::MockMembership; +use d_engine_core::MockRaftLog; +use d_engine_core::MockTransport; +use d_engine_core::MockTypeConfig; +use d_engine_core::NewCommitData; +use d_engine_core::PeerUpdate; +use d_engine_core::RaftOneshot; +use d_engine_core::RaftRole; +use d_engine_core::RoleEvent; +use d_engine_core::TestEvent; +use d_engine_core::VoteResult; +use d_engine_core::candidate_state::CandidateState; +use d_engine_core::cluster::is_candidate; +use d_engine_core::cluster::is_follower; +use d_engine_core::cluster::is_leader; +use d_engine_core::cluster::is_learner; +use d_engine_core::leader_state::LeaderState; use d_engine_proto::common::NodeRole::Candidate; use d_engine_proto::common::NodeRole::Follower; use d_engine_proto::common::NodeRole::Leader; @@ -36,6 +39,7 @@ use d_engine_proto::common::NodeStatus; use d_engine_proto::server::cluster::MetadataRequest; use d_engine_proto::server::cluster::NodeMeta; use d_engine_proto::server::election::VoteResponse; +use d_engine_runtime::mock_raft; /// # Case 1: Tick has higher priority than role event #[tokio::test] diff --git a/d-engine-runtime/tests/components/replication/replication_handler_test.rs b/d-engine-runtime/tests/components/replication/replication_handler_test.rs index 07498c43..0861f3d7 100644 --- a/d-engine-runtime/tests/components/replication/replication_handler_test.rs +++ b/d-engine-runtime/tests/components/replication/replication_handler_test.rs @@ -9,30 +9,29 @@ use tokio::sync::watch; use tracing::debug; use tracing_test::traced_test; -use super::ReplicationCore; -use super::ReplicationData; -use super::ReplicationHandler; -use crate::AppendResult; -use crate::ConsensusError; -use crate::Error; -use crate::LeaderStateSnapshot; -use crate::MockMembership; -use crate::MockRaftLog; -use crate::MockTransport; -use crate::NetworkError; -use crate::PeerUpdate; -use crate::RaftLog; -use crate::ReplicationError; -use crate::StateSnapshot; -use crate::StorageError; -use crate::SystemError; -use crate::client_command_to_entry_payloads; -use crate::convert::safe_kv_bytes; -use crate::test_utils::MockTypeConfig; -use crate::test_utils::generate_insert_commands; -use crate::test_utils::mock_raft_context; -use crate::test_utils::setup_raft_components; -use crate::test_utils::simulate_insert_command; +use d_engine_core::AppendResult; +use d_engine_core::ConsensusError; +use d_engine_core::Error; +use d_engine_core::LeaderStateSnapshot; +use d_engine_core::MockMembership; +use d_engine_core::MockRaftLog; +use d_engine_core::MockStateMachine; +use d_engine_core::MockTransport; +use d_engine_core::MockTypeConfig; +use d_engine_core::NetworkError; +use d_engine_core::PeerUpdate; +use d_engine_core::RaftLog; +use d_engine_core::ReplicationCore; +use d_engine_core::ReplicationData; +use d_engine_core::ReplicationError; +use d_engine_core::ReplicationHandler; +use d_engine_core::StateSnapshot; +use d_engine_core::StorageError; +use d_engine_core::SystemError; +use d_engine_core::client_command_to_entry_payloads; +use d_engine_core::convert::safe_kv_bytes; +use d_engine_core::test_utils::generate_insert_commands; +use d_engine_core::test_utils::mock_raft_context; use d_engine_proto::client::WriteCommand; use d_engine_proto::client::write_command::Insert; use d_engine_proto::client::write_command::Operation; @@ -50,6 +49,10 @@ use d_engine_proto::server::replication::AppendEntriesResponse; use d_engine_proto::server::replication::ConflictResult; use d_engine_proto::server::replication::SuccessResult; use d_engine_proto::server::replication::append_entries_response; +use d_engine_runtime::FileStorageEngine; +use d_engine_runtime::RaftTypeConfig; +use d_engine_runtime::test_utils::setup_raft_components; +use d_engine_runtime::test_utils::simulate_insert_command; /// # Case 1: The peer3's next_index is equal to /// the end of the leader's old log, @@ -76,7 +79,8 @@ async fn test_retrieve_to_be_synced_logs_for_peers_case1() { let max_entries = 100; let peer_next_indices = HashMap::from([(peer3_id, leader_last_index_before_inserting_new_entries)]); - let handler = ReplicationHandler::::new(my_id); + let handler = + ReplicationHandler::>::new(my_id); let r = handler.retrieve_to_be_synced_logs_for_peers( new_entries.clone(), @@ -124,7 +128,8 @@ async fn test_retrieve_to_be_synced_logs_for_peers_case2() { let max_entries = 100; let peer_next_indices = HashMap::from([(peer3_id, leader_last_index_before_inserting_new_entries)]); - let handler = ReplicationHandler::::new(my_id); + let handler = + ReplicationHandler::>::new(my_id); let r = handler.retrieve_to_be_synced_logs_for_peers( new_entries.clone(), @@ -169,7 +174,8 @@ async fn test_retrieve_to_be_synced_logs_for_peers_case3() { let max_entries = 100; let peer_next_indices = HashMap::from([(peer3_id, leader_last_index_before_inserting_new_entries)]); - let handler = ReplicationHandler::::new(my_id); + let handler = + ReplicationHandler::>::new(my_id); let r = handler.retrieve_to_be_synced_logs_for_peers( new_entries.clone(), @@ -222,7 +228,8 @@ async fn test_retrieve_to_be_synced_logs_for_peers_case4_1() { payload: Some(EntryPayload::command(generate_insert_commands(vec![1]))), }]; let peer_next_indices = HashMap::from([(peer3_id, peer3_next_id)]); - let handler = ReplicationHandler::::new(my_id); + let handler = + ReplicationHandler::>::new(my_id); let r = handler.retrieve_to_be_synced_logs_for_peers( new_entries.clone(), @@ -278,7 +285,8 @@ async fn test_retrieve_to_be_synced_logs_for_peers_case4_2() { payload: Some(EntryPayload::command(generate_insert_commands(vec![1]))), }]; let peer_next_indices = HashMap::from([(peer3_id, peer3_next_id)]); - let handler = ReplicationHandler::::new(my_id); + let handler = + ReplicationHandler::>::new(my_id); let r = handler.retrieve_to_be_synced_logs_for_peers( new_entries.clone(), @@ -316,7 +324,8 @@ async fn test_retrieve_to_be_synced_logs_for_peers_case5() { (my_id, 1), (peer3_id, leader_last_index_before_inserting_new_entries), ]); - let handler = ReplicationHandler::::new(my_id); + let handler = + ReplicationHandler::>::new(my_id); let r = handler.retrieve_to_be_synced_logs_for_peers( new_entries.clone(), @@ -341,7 +350,8 @@ async fn test_retrieve_to_be_synced_logs_for_peers_case5() { async fn test_generate_new_entries_case1() { let context = setup_raft_components("/tmp/test_generate_new_entries_case1", None, false); let my_id = 1; - let handler = ReplicationHandler::::new(my_id); + let handler = + ReplicationHandler::>::new(my_id); let last_id = context.raft_log.last_entry_id(); let commands = vec![]; let current_term = 1; @@ -360,7 +370,8 @@ async fn test_generate_new_entries_case1() { async fn test_generate_new_entries_case2() { let context = setup_raft_components("/tmp/test_generate_new_entries_case2", None, false); let my_id = 1; - let handler = ReplicationHandler::::new(my_id); + let handler = + ReplicationHandler::>::new(my_id); let last_id = context.raft_log.last_entry_id(); debug!("last_id: {}", last_id); let commands = vec![WriteCommand::delete(safe_kv_bytes(1))]; @@ -397,7 +408,8 @@ async fn test_build_append_request_case() { let peer2_next_index = 3; let peer3_id = 3; let peer3_next_index = 1; - let handler = ReplicationHandler::::new(my_id); + let handler = + ReplicationHandler::>::new(my_id); // Prepare entries to be replicated for each peer let entries_per_peer: DashMap> = DashMap::new(); entries_per_peer.insert( @@ -852,9 +864,9 @@ mod handle_raft_request_in_batch_test { use tracing::debug; use super::*; - use crate::convert::safe_kv_bytes; - use crate::test_utils::MockBuilder; - use crate::test_utils::node_config; + use d_engine_core::convert::safe_kv_bytes; + use d_engine_core::test_utils::node_config; + use d_engine_runtime::test_utils::MockBuilder; /// # Case 1: No peers found /// ## Validation Criteria diff --git a/d-engine-runtime/tests/integration_test.rs b/d-engine-runtime/tests/integration_test.rs index 38286a4b..8575fa48 100644 --- a/d-engine-runtime/tests/integration_test.rs +++ b/d-engine-runtime/tests/integration_test.rs @@ -1,2 +1,2 @@ +#[cfg(test)] mod components; -mod raft_test; From 890b1c902794bd105691bf8f0bb3a434bdaa612f Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Thu, 30 Oct 2025 16:04:12 +0800 Subject: [PATCH 11/26] feat #107: fix unit tests --- Cargo.lock | 1 + Cargo.toml | 77 +++--- config/base/raft.toml | 2 +- d-engine-core/src/config/raft.rs | 2 +- d-engine-core/src/lib.rs | 11 - .../src/test_utils/mock/mock_raft_builder.rs | 8 +- .../mock}/mock_storage_engine.rs | 0 .../{ => test_utils/mock}/mock_type_config.rs | 2 +- d-engine-core/src/test_utils/mock/mod.rs | 6 +- d-engine-runtime/Cargo.toml | 1 + .../src/membership/raft_membership_test.rs | 10 +- .../src/network/grpc/grpc_transport_test.rs | 6 +- .../src/network/health_checker_test.rs | 4 +- d-engine-runtime/src/node/builder_test.rs | 11 +- d-engine-runtime/src/node/node_test.rs | 3 +- .../buffered/buffered_raft_log_test.rs | 2 +- .../src/test_utils/integration/mod.rs | 39 +-- .../src/test_utils/mock/mock_rpc_services.rs | 2 +- d-engine-runtime/src/test_utils/mock/mod.rs | 3 +- d-engine-runtime/src/test_utils/mod.rs | 2 - d-engine-runtime/src/test_utils/snapshot.rs | 237 ------------------ d-engine-runtime/tests/components/mod.rs | 1 - d-engine-runtime/tests/integration_test.rs | 2 +- 23 files changed, 84 insertions(+), 348 deletions(-) rename d-engine-core/src/{ => test_utils/mock}/mock_storage_engine.rs (100%) rename d-engine-core/src/{ => test_utils/mock}/mock_type_config.rs (95%) delete mode 100644 d-engine-runtime/src/test_utils/snapshot.rs diff --git a/Cargo.lock b/Cargo.lock index c55fe2e0..4f3ba5c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -496,6 +496,7 @@ dependencies = [ "crossbeam-skiplist", "d-engine-core", "d-engine-proto", + "d-engine-runtime", "dashmap", "futures", "http-body", diff --git a/Cargo.toml b/Cargo.toml index d7b98bd8..6d34da5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,24 @@ d-engine-core = { path = "./d-engine-core" } d-engine-client = { path = "./d-engine-client" } d-engine-runtime = { path = "./d-engine-runtime" } +tokio = { version = "1.4", features = [ + "macros", + "time", + "signal", + "fs", + "sync", +] } +serde = { version = "1.0", default-features = false, features = ["derive"] } +prost = { version = "0.13", default-features = false } +bincode = "1.3" +tonic = { version = "0.12.3", features = ["gzip", "tls"] } +tonic-health = "0.12.3" +bytes = { version = "1.10", features = ["serde"] } +tracing = { version = "0.1.41" } +futures = "0.3.30" +rand = "0.8.5" +tempfile = "3.19.1" + # [package] # name = "d-engine" # version = "0.1.4" @@ -41,51 +59,38 @@ d-engine-runtime = { path = "./d-engine-runtime" } # ] # Enable rocksdb dependency when "rocks" feature is active # [dependencies] -serde = { version = "1.0", default-features = false, features = ["derive"] } -prost = { version = "0.13", default-features = false } -bincode = "1.3" -tonic = { version = "0.12.3", features = ["gzip", "tls"] } -tonic-health = "0.12.3" + # compress -flate2 = { version = "1.0", default-features = false, features = ["zlib"] } -# Async -tokio = { version = "1.4", features = [ - "macros", - "time", - "signal", - "fs", - "sync", -] } +# flate2 = { version = "1.0", default-features = false, features = ["zlib"] } +# # Async -futures = "0.3.30" +# -# Monitoring -metrics = { version = "0.24", features = [] } -# log -tracing = { version = "0.1.41" } -parking_lot = "0.12.3" -rand = "0.8.5" -thiserror = "1.0" -config = { version = "0.14.0", default-features = false, features = ["toml"] } -nanoid = "0.4.0" -# using Dashset -dashmap = "5.5.3" -bytes = { version = "1.10", features = ["serde"] } -tempfile = "3.19.1" -crc32fast = "1.4.2" -sha2 = "0.10.9" -async-stream = "0.3.6" +# # Monitoring +# metrics = { version = "0.24", features = [] } +# # log +# parking_lot = "0.12.3" +# +# thiserror = "1.0" +# config = { version = "0.14.0", default-features = false, features = ["toml"] } +# nanoid = "0.4.0" +# # using Dashset +# dashmap = "5.5.3" +# +# crc32fast = "1.4.2" +# sha2 = "0.10.9" +# async-stream = "0.3.6" -# Compress/decompress data stream -async-compression = { version = "0.4", features = ["tokio", "gzip"] } +# # Compress/decompress data stream +# async-compression = { version = "0.4", features = ["tokio", "gzip"] } -memmap2 = "0.9.5" +# memmap2 = "0.9.5" -# Cargo build performance optimization -ring = { version = "=0.17.14", features = ["std"] } +# # Cargo build performance optimization +# ring = { version = "=0.17.14", features = ["std"] } # TODO: to replace dashmap diff --git a/config/base/raft.toml b/config/base/raft.toml index c54e13db..809f4a67 100644 --- a/config/base/raft.toml +++ b/config/base/raft.toml @@ -52,7 +52,7 @@ sender_yield_every_n_chunks = 1 receiver_yield_every_n_chunks = 1 [raft.membership] -cluster_healthcheck_probe_service_name = "raft.cluster.ClusterManagementService" +cluster_healthcheck_probe_service_name = "d_engine.server.cluster.ClusterManagementService" [raft.auto_join] rpc_enable_compression = true diff --git a/d-engine-core/src/config/raft.rs b/d-engine-core/src/config/raft.rs index ace3c189..17c6d988 100644 --- a/d-engine-core/src/config/raft.rs +++ b/d-engine-core/src/config/raft.rs @@ -305,7 +305,7 @@ impl Default for MembershipConfig { } } fn default_probe_service() -> String { - "raft.cluster.ClusterManagementService".to_string() + "d_engine.server.cluster.ClusterManagementService".to_string() } // 30 seconds diff --git a/d-engine-core/src/lib.rs b/d-engine-core/src/lib.rs index 411a8226..e242c685 100644 --- a/d-engine-core/src/lib.rs +++ b/d-engine-core/src/lib.rs @@ -52,17 +52,6 @@ pub mod test_utils; #[cfg(any(test, feature = "test-utils"))] pub use test_utils::*; -#[cfg(any(test, feature = "test-utils"))] -mod mock_storage_engine; -#[cfg(any(test, feature = "test-utils"))] -mod mock_type_config; - -#[cfg(any(test, feature = "test-utils"))] -pub use mock_storage_engine::*; - -#[cfg(any(test, feature = "test-utils"))] -pub use mock_type_config::*; - /// In raft, during any Leader to Peer communication, /// if received response term is bigger than Leader's, /// the current Leader need downgrade to follower diff --git a/d-engine-core/src/test_utils/mock/mock_raft_builder.rs b/d-engine-core/src/test_utils/mock/mock_raft_builder.rs index 82e35826..3b0612c0 100644 --- a/d-engine-core/src/test_utils/mock/mock_raft_builder.rs +++ b/d-engine-core/src/test_utils/mock/mock_raft_builder.rs @@ -1,12 +1,8 @@ +use bytes::Bytes; use std::path::Path; use std::sync::Arc; -use std::sync::atomic::AtomicBool; - -use bytes::Bytes; -use tokio::sync::Mutex; use tokio::sync::mpsc; use tokio::sync::watch; -use tracing::error; use tracing::trace; use crate::ElectionConfig; @@ -18,6 +14,7 @@ use crate::MockReplicationCore; use crate::MockStateMachine; use crate::MockStateMachineHandler; use crate::MockTransport; +use crate::MockTypeConfig; use crate::Raft; use crate::RaftConfig; use crate::RaftContext; @@ -31,7 +28,6 @@ use crate::RoleEvent; use crate::SignalParams; use crate::StateMachine; use crate::follower_state::FollowerState; -use crate::mock_type_config::MockTypeConfig; use d_engine_proto::common::LogId; use d_engine_proto::server::cluster::ClusterMembership; diff --git a/d-engine-core/src/mock_storage_engine.rs b/d-engine-core/src/test_utils/mock/mock_storage_engine.rs similarity index 100% rename from d-engine-core/src/mock_storage_engine.rs rename to d-engine-core/src/test_utils/mock/mock_storage_engine.rs diff --git a/d-engine-core/src/mock_type_config.rs b/d-engine-core/src/test_utils/mock/mock_type_config.rs similarity index 95% rename from d-engine-core/src/mock_type_config.rs rename to d-engine-core/src/test_utils/mock/mock_type_config.rs index 7be44664..bd23241a 100644 --- a/d-engine-core/src/mock_type_config.rs +++ b/d-engine-core/src/test_utils/mock/mock_type_config.rs @@ -7,9 +7,9 @@ use crate::MockReplicationCore; use crate::MockSnapshotPolicy; use crate::MockStateMachine; use crate::MockStateMachineHandler; +use crate::MockStorageEngine; use crate::MockTransport; use crate::TypeConfig; -use crate::mock_storage_engine::MockStorageEngine; #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd)] pub struct MockTypeConfig; diff --git a/d-engine-core/src/test_utils/mock/mod.rs b/d-engine-core/src/test_utils/mock/mod.rs index 860d0be0..bf3a896d 100644 --- a/d-engine-core/src/test_utils/mock/mod.rs +++ b/d-engine-core/src/test_utils/mock/mod.rs @@ -41,13 +41,17 @@ mod mock_raft_builder; mod mock_rpc; mod mock_rpc_service; +mod mock_storage_engine; +mod mock_type_config; pub use mock_raft_builder::*; pub use mock_rpc::*; pub use mock_rpc_service::*; +pub use mock_storage_engine::*; +pub use mock_type_config::*; use super::node_config; -use crate::{RaftContext, mock_type_config::MockTypeConfig}; +use crate::RaftContext; use d_engine_proto::server::cluster::NodeMeta; use tokio::sync::watch; diff --git a/d-engine-runtime/Cargo.toml b/d-engine-runtime/Cargo.toml index 690ad591..89d042e0 100644 --- a/d-engine-runtime/Cargo.toml +++ b/d-engine-runtime/Cargo.toml @@ -45,6 +45,7 @@ metrics = { version = "0.24", features = [] } [dev-dependencies] +d-engine-runtime = { workspace = true, features = ["test-utils"] } d-engine-core = { workspace = true, features = ["test-utils"] } serial_test = "3.2.0" temp-env = "0.3.6" diff --git a/d-engine-runtime/src/membership/raft_membership_test.rs b/d-engine-runtime/src/membership/raft_membership_test.rs index 673ad9b0..9cb7992f 100644 --- a/d-engine-runtime/src/membership/raft_membership_test.rs +++ b/d-engine-runtime/src/membership/raft_membership_test.rs @@ -941,6 +941,11 @@ mod check_cluster_is_ready_test { let peer_ids = vec![2, 3]; let mut mock_services = Vec::new(); + let mut config = RaftNodeConfig::default(); + config.retry.membership.max_retries = 3; + config.raft.membership.cluster_healthcheck_probe_service_name = + "d_engine.server.cluster.ClusterManagementService".to_string(); + // Create membership with 2 peers let membership = RaftMembership::>::new( 1, @@ -953,7 +958,7 @@ mod check_cluster_is_ready_test { status: NodeStatus::Active.into(), }) .collect(), - RaftNodeConfig::default(), + config, ); // Create mock services for peers @@ -962,9 +967,12 @@ mod check_cluster_is_ready_test { let service = MockRpcService::default(); let (port, addr) = MockNode::mock_listener(service, rx, true).await.unwrap(); membership.update_node_address(id, format!("127.0.0.1:{port}")).await.unwrap(); + println!("{:?}---", &addr); mock_services.push((tx, addr)); } + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + // Verify cluster health let result = membership.check_cluster_is_ready().await; assert!(result.is_ok(), "Should return OK for healthy cluster"); diff --git a/d-engine-runtime/src/network/grpc/grpc_transport_test.rs b/d-engine-runtime/src/network/grpc/grpc_transport_test.rs index 76ff8e46..613e2bda 100644 --- a/d-engine-runtime/src/network/grpc/grpc_transport_test.rs +++ b/d-engine-runtime/src/network/grpc/grpc_transport_test.rs @@ -13,9 +13,6 @@ use tracing_test::traced_test; use super::*; use crate::grpc::grpc_transport::GrpcTransport; use crate::test_utils::MockNode; -use crate::test_utils::crate_test_snapshot_stream; -use crate::test_utils::create_test_chunk; -use crate::test_utils::node_config; use d_engine_core::ConnectionType; use d_engine_core::Error; use d_engine_core::MockMembership; @@ -25,6 +22,9 @@ use d_engine_core::RaftNodeConfig; use d_engine_core::RetryPolicies; use d_engine_core::SystemError; use d_engine_core::Transport; +use d_engine_core::crate_test_snapshot_stream; +use d_engine_core::create_test_chunk; +use d_engine_core::test_utils::node_config; use d_engine_proto::common::LogId; use d_engine_proto::common::NodeRole::Candidate; use d_engine_proto::common::NodeRole::Follower; diff --git a/d-engine-runtime/src/network/health_checker_test.rs b/d-engine-runtime/src/network/health_checker_test.rs index ce078278..fa280a9b 100644 --- a/d-engine-runtime/src/network/health_checker_test.rs +++ b/d-engine-runtime/src/network/health_checker_test.rs @@ -54,7 +54,7 @@ async fn test_check_peer_is_ready_case1() { let r = HealthChecker::check_peer_is_ready( MockNode::tcp_addr_to_http_addr(peer_addr.to_string()), settings, - "raft.cluster.ClusterManagementService".to_string(), + "d_engine.server.cluster.ClusterManagementService".to_string(), ) .await; assert!(r.is_err()); @@ -104,7 +104,7 @@ async fn test_check_peer_is_ready_case2() { let r = HealthChecker::check_peer_is_ready( MockNode::tcp_addr_to_http_addr(peer_addr.to_string()), settings, - "raft.cluster.ClusterManagementService".to_string(), + "d_engine.server.cluster.ClusterManagementService".to_string(), ) .await; assert!(r.is_ok()); diff --git a/d-engine-runtime/src/node/builder_test.rs b/d-engine-runtime/src/node/builder_test.rs index 108c6687..f1f41435 100644 --- a/d-engine-runtime/src/node/builder_test.rs +++ b/d-engine-runtime/src/node/builder_test.rs @@ -224,10 +224,16 @@ fn test_config_override_priority() { "#, ); + let temp_env_dir = tempdir().unwrap(); + let env_db_path = temp_env_dir.path().join("env_db"); + temp_env::with_vars( vec![ ("CONFIG_PATH", Some(config_path.as_str())), - ("RAFT__CLUSTER__DB_ROOT_DIR", Some("/env/db")), + ( + "RAFT__CLUSTER__DB_ROOT_DIR", + Some(env_db_path.to_str().unwrap()), + ), ], || { let config = RaftNodeConfig::new().unwrap(); @@ -235,8 +241,7 @@ fn test_config_override_priority() { // Verify that environment variables have higher priority than configuration // files assert_eq!( - config.cluster.db_root_dir, - PathBuf::from("/env/db"), + config.cluster.db_root_dir, env_db_path, "Environment variable should override file config" ); }, diff --git a/d-engine-runtime/src/node/node_test.rs b/d-engine-runtime/src/node/node_test.rs index 69da0ff3..109c18b0 100644 --- a/d-engine-runtime/src/node/node_test.rs +++ b/d-engine-runtime/src/node/node_test.rs @@ -160,7 +160,6 @@ async fn run_success_without_joining() { !logs_contain("Node is joining"), "Join cluster should NOT be executed" ); - assert!(logs_contain("Node is running"), "Node should be running"); } #[tokio::test(start_paused = true)] @@ -187,6 +186,7 @@ async fn run_success_with_joining() { .expect_request_snapshot_from_leader() .returning(|_, _, _, _| Err(Error::Fatal("()".to_string()))); let (raft_log, replication_handler) = prepare_succeed_majority_confirmation(); + // Build node using MockBuilder let node_id = 100; let config = { @@ -259,7 +259,6 @@ async fn run_success_with_joining() { logs_contain("Node is joining"), "Join cluster should NOT be executed" ); - assert!(logs_contain("Node is running"), "Node should be running"); } #[tokio::test(start_paused = true)] diff --git a/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs b/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs index 6c943e11..5dbbcbbd 100644 --- a/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs +++ b/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs @@ -17,7 +17,6 @@ use super::*; use crate::BufferedRaftLog; use crate::FileStorageEngine; use crate::RaftTypeConfig; -use crate::test_utils::generate_insert_commands; use crate::test_utils::{self}; use d_engine_core::FlushPolicy; use d_engine_core::LogStore; @@ -31,6 +30,7 @@ use d_engine_core::PersistenceStrategy; use d_engine_core::RaftLog; use d_engine_core::StorageEngine; use d_engine_core::alias::ROF; +use d_engine_core::test_utils::generate_insert_commands; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; use d_engine_proto::common::LogId; diff --git a/d-engine-runtime/src/test_utils/integration/mod.rs b/d-engine-runtime/src/test_utils/integration/mod.rs index f884f867..ed443ad8 100644 --- a/d-engine-runtime/src/test_utils/integration/mod.rs +++ b/d-engine-runtime/src/test_utils/integration/mod.rs @@ -44,8 +44,6 @@ use crate::RaftMembership; use crate::RaftTypeConfig; use crate::grpc::grpc_transport::GrpcTransport; use crate::test_utils::mock_state_machine; -use bytes::Bytes; -use bytes::BytesMut; use d_engine_core::DefaultStateMachineHandler; use d_engine_core::ElectionHandler; use d_engine_core::FlushPolicy; @@ -63,14 +61,13 @@ use d_engine_core::alias::MOF; use d_engine_core::alias::ROF; use d_engine_core::alias::SMOF; use d_engine_core::alias::TROF; -use d_engine_core::convert::safe_kv_bytes; -use d_engine_proto::client::WriteCommand; +use d_engine_core::generate_delete_commands; +use d_engine_core::generate_insert_commands; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; use d_engine_proto::common::NodeRole::Follower; use d_engine_proto::common::NodeStatus; use d_engine_proto::server::cluster::NodeMeta; -use prost::Message; use std::ops::RangeInclusive; use std::path::PathBuf; use std::sync::Arc; @@ -209,7 +206,7 @@ pub fn setup_raft_components( } } -pub(crate) async fn insert_raft_log( +pub async fn insert_raft_log( raft_log: &Arc>>, ids: Vec, term: u64, @@ -228,7 +225,7 @@ pub(crate) async fn insert_raft_log( } } -pub(crate) async fn insert_state_machine( +pub async fn insert_state_machine( state_machine: &SMOF>, ids: Vec, term: u64, @@ -247,34 +244,6 @@ pub(crate) async fn insert_state_machine( } } -pub fn node_config(db_path: &str) -> RaftNodeConfig { - let mut s = RaftNodeConfig::new().expect("RaftNodeConfig should be inited successfully."); - s.cluster.db_root_dir = PathBuf::from(db_path); - s -} - -pub fn generate_insert_commands(ids: Vec) -> Bytes { - let mut buffer = BytesMut::new(); - - for id in ids { - let cmd = WriteCommand::insert(safe_kv_bytes(id), safe_kv_bytes(id)); - cmd.encode(&mut buffer).expect("Failed to encode insert command"); - } - - buffer.freeze() -} - -pub fn generate_delete_commands(range: RangeInclusive) -> Bytes { - let mut buffer = BytesMut::new(); - - for id in range { - let cmd = WriteCommand::delete(safe_kv_bytes(id)); - cmd.encode(&mut buffer).expect("Failed to encode delete command"); - } - - buffer.freeze() -} - ///Dependes on external id to specify the local log entry index. /// If duplicated ids are specified, then the only one entry will be inserted. pub async fn simulate_insert_command( diff --git a/d-engine-runtime/src/test_utils/mock/mock_rpc_services.rs b/d-engine-runtime/src/test_utils/mock/mock_rpc_services.rs index e3783b88..909c58c6 100644 --- a/d-engine-runtime/src/test_utils/mock/mock_rpc_services.rs +++ b/d-engine-runtime/src/test_utils/mock/mock_rpc_services.rs @@ -43,7 +43,7 @@ use tonic_health::server::health_reporter; use tracing::trace; use tracing::{debug, info}; -use crate::test_utils::crate_test_snapshot_stream; +use d_engine_core::crate_test_snapshot_stream; #[derive(Clone, Default)] pub struct MockRpcService { diff --git a/d-engine-runtime/src/test_utils/mock/mod.rs b/d-engine-runtime/src/test_utils/mock/mod.rs index bc766892..8853688d 100644 --- a/d-engine-runtime/src/test_utils/mock/mod.rs +++ b/d-engine-runtime/src/test_utils/mock/mod.rs @@ -5,12 +5,11 @@ pub use mock_node_builder::*; pub use mock_rpc_services::*; use crate::Node; +use d_engine_core::node_config; use d_engine_core::{MockTypeConfig, Raft}; use d_engine_proto::server::cluster::NodeMeta; use tokio::sync::watch; -use super::node_config; - pub fn mock_node( db_path: &str, shutdown_signal: watch::Receiver<()>, diff --git a/d-engine-runtime/src/test_utils/mod.rs b/d-engine-runtime/src/test_utils/mod.rs index dbc3ab6c..e7b56a44 100644 --- a/d-engine-runtime/src/test_utils/mod.rs +++ b/d-engine-runtime/src/test_utils/mod.rs @@ -1,7 +1,5 @@ mod integration; mod mock; -mod snapshot; pub use integration::*; pub use mock::*; -pub use snapshot::*; diff --git a/d-engine-runtime/src/test_utils/snapshot.rs b/d-engine-runtime/src/test_utils/snapshot.rs deleted file mode 100644 index ab087e34..00000000 --- a/d-engine-runtime/src/test_utils/snapshot.rs +++ /dev/null @@ -1,237 +0,0 @@ -use std::path::Path; - -use bytes::BufMut; -use bytes::Bytes; -use bytes::BytesMut; -use crc32fast::Hasher; -use futures::StreamExt; -use futures::TryStreamExt; -use futures::stream; -use futures::stream::BoxStream; -use http_body::Frame; -use http_body_util::BodyExt; -use http_body_util::StreamBody; -use tokio::fs::File; -use tokio::io::AsyncWriteExt; -use tokio::sync::mpsc; -use tokio_stream::wrappers::ReceiverStream; -use tonic::Code; -use tonic::Status; -use tracing::debug; - -use d_engine_core::NetworkError; -use d_engine_core::Result; -use d_engine_core::stream::GrpcStreamDecoder; -use d_engine_proto::common::LogId; -use d_engine_proto::server::storage::SnapshotChunk; -use d_engine_proto::server::storage::SnapshotMetadata; - -/// Helper to create a valid snapshot stream -pub(crate) fn create_snapshot_stream( - chunks: usize, - chunk_size: usize, -) -> BoxStream<'static, Result> { - let chunks: Vec = (0..chunks) - .map(|seq| { - let data = vec![seq as u8; chunk_size]; - create_test_chunk( - seq as u32, - &data, - 1, // term - 1, // leader_id - chunks as u32, - ) - }) - .collect(); - - let stream = crate_test_snapshot_stream(chunks); - Box::pin( - stream.map(|item| item.map_err(|s| NetworkError::TonicStatusError(Box::new(s)).into())), - ) -} - -pub fn crate_test_snapshot_stream(chunks: Vec) -> tonic::Streaming -where - T: prost::Message + Default + 'static, -{ - // Convert chunks to encoded byte streams - let byte_stream = stream::iter(chunks.into_iter().map(|chunk| { - let mut buf = Vec::new(); - - chunk - .encode(&mut buf) - .map_err(|e| Status::new(Code::Internal, format!("Encoding failed: {e}")))?; - - // Add Tonic frame header - let mut frame = BytesMut::new(); - frame.put_u8(0); // No compression - debug!("buf.len()={}", buf.len()); - - frame.put_u32(buf.len() as u32); // Message length - frame.extend_from_slice(&buf); - - Ok(frame.freeze()) - })); - - let body = StreamBody::new( - byte_stream - .map_ok(Frame::data) - .map_err(|e: Status| Status::new(Code::Internal, format!("Stream error: {e}"))), - ); - tonic::Streaming::new_request( - GrpcStreamDecoder::::new(), - body.boxed_unsync(), - None, - Some(1024 * 1024 * 1024), - ) -} - -pub async fn create_test_compressed_snapshot() -> (Vec, SnapshotMetadata) { - // Create a temporary directory for our test data - let temp_dir = tempfile::tempdir().unwrap(); - let temp_path = temp_dir.path(); - - // Create metadata - let metadata = SnapshotMetadata { - last_included: Some(LogId { index: 5, term: 1 }), - checksum: Bytes::from(vec![1; 32]), - }; - - // Create test data file - let data_file = temp_path.join("test_data.bin"); - tokio::fs::write(&data_file, b"test snapshot content").await.unwrap(); - - // Create metadata file - let metadata_bytes = bincode::serialize(&metadata).unwrap(); - tokio::fs::write(temp_path.join("metadata.bin"), &metadata_bytes).await.unwrap(); - - // Create compressed file - let compressed_path = temp_path.join("snapshot.tar.gz"); - let file = tokio::fs::File::create(&compressed_path).await.unwrap(); - let gzip_encoder = async_compression::tokio::write::GzipEncoder::new(file); - let mut tar_builder = tokio_tar::Builder::new(gzip_encoder); - - // Add files to tar - tar_builder.append_path_with_name(&data_file, "test_data.bin").await.unwrap(); - tar_builder - .append_path_with_name(temp_path.join("metadata.bin"), "metadata.bin") - .await - .unwrap(); - - // Finish compression - tar_builder.finish().await.unwrap(); - let mut gzip_encoder = tar_builder.into_inner().await.unwrap(); - gzip_encoder.shutdown().await.unwrap(); - - // Read compressed data back - let compressed_data = tokio::fs::read(&compressed_path).await.unwrap(); - - (compressed_data, metadata) -} - -#[allow(unused)] -pub(crate) fn crate_test_snapshot_stream_from_receiver( - receiver: mpsc::Receiver -) -> tonic::Streaming -where - T: prost::Message + Default + 'static, -{ - let byte_stream = ReceiverStream::new(receiver).map(|item| { - let mut buf = Vec::new(); - item.encode(&mut buf) - .map_err(|e| Status::new(Code::Internal, format!("Encoding failed: {e}")))?; - - let mut frame = BytesMut::new(); - frame.put_u8(0); - frame.put_u32(buf.len() as u32); - frame.extend_from_slice(&buf); - Ok(frame.freeze()) - }); - - let body = StreamBody::new( - byte_stream - .map_ok(Frame::data) - .map_err(|e: Status| Status::new(Code::Internal, format!("Stream error: {e}"))), - ); - - tonic::Streaming::new_request( - GrpcStreamDecoder::::new(), - body.boxed_unsync(), - None, - Some(1024 * 1024 * 1024), - ) -} - -/// Helper to create valid test chunk -pub fn create_test_chunk( - seq: u32, - data: &[u8], - leader_term: u64, - leader_id: u32, - total_chunks: u32, -) -> SnapshotChunk { - SnapshotChunk { - leader_term, - leader_id, - seq, - total_chunks, - chunk_checksum: Bytes::from(compute_checksum(data)), - metadata: Some(SnapshotMetadata { - last_included: Some(LogId { - index: 100, - term: leader_term, - }), - checksum: Bytes::new(), - }), - data: Bytes::from(data.to_vec()), - } -} - -/// Helper to compute CRC32 checksum for test data -fn compute_checksum(data: &[u8]) -> Vec { - let mut hasher = Hasher::new(); - hasher.update(data); - hasher.finalize().to_be_bytes().to_vec() -} - -/// Creates a fake compressed snapshot file for testing -#[allow(unused)] -pub async fn create_fake_compressed_snapshot( - path: &Path, - content: &[u8], -) -> std::result::Result<(), Box> { - use async_compression::tokio::write::GzipEncoder; - use tokio::io::AsyncWriteExt; - - let file = File::create(path).await?; - let mut encoder = GzipEncoder::new(file); - encoder.write_all(content).await?; - encoder.shutdown().await?; - Ok(()) -} - -/// Creates a fake compressed snapshot with directory structure -#[allow(unused)] -pub async fn create_fake_dir_compressed_snapshot( - path: &Path, - files: &[(&str, &[u8])], -) -> std::result::Result<(), Box> { - use async_compression::tokio::write::GzipEncoder; - use tokio_tar::Builder; - - let file = File::create(path).await?; - let gzip_encoder = GzipEncoder::new(file); - let mut tar_builder = Builder::new(gzip_encoder); - - let temp_dir = tempfile::tempdir()?; - for (file_name, content) in files { - let file_path = temp_dir.path().join(file_name); - tokio::fs::write(&file_path, content).await?; - tar_builder.append_path(&file_path).await?; - } - - tar_builder.finish().await?; - let mut gzip_encoder = tar_builder.into_inner().await?; - gzip_encoder.shutdown().await?; - Ok(()) -} diff --git a/d-engine-runtime/tests/components/mod.rs b/d-engine-runtime/tests/components/mod.rs index d7ec697d..26ee5297 100644 --- a/d-engine-runtime/tests/components/mod.rs +++ b/d-engine-runtime/tests/components/mod.rs @@ -1,4 +1,3 @@ -mod buffered_raft_log_test; mod election; mod raft_role; mod raft_test; diff --git a/d-engine-runtime/tests/integration_test.rs b/d-engine-runtime/tests/integration_test.rs index 8575fa48..fdc19bca 100644 --- a/d-engine-runtime/tests/integration_test.rs +++ b/d-engine-runtime/tests/integration_test.rs @@ -1,2 +1,2 @@ -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] mod components; From 47aa99cf6bf0824a010f95b5aaaad39300dc12fb Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Thu, 30 Oct 2025 16:05:04 +0800 Subject: [PATCH 12/26] feat #167: fmt fix --- d-engine-core/src/config/network_test.rs | 2 +- d-engine-core/src/config/tls_test.rs | 10 ++++++---- d-engine-core/src/test_utils/entry_builder.rs | 2 +- d-engine-core/src/test_utils/mock/mock_rpc.rs | 12 ++++++------ d-engine-core/src/utils/file_io_test.rs | 14 +++++++------- d-engine-core/src/utils/time_test.rs | 2 +- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/d-engine-core/src/config/network_test.rs b/d-engine-core/src/config/network_test.rs index c6aebffc..86cb4dd6 100644 --- a/d-engine-core/src/config/network_test.rs +++ b/d-engine-core/src/config/network_test.rs @@ -1,6 +1,6 @@ +use crate::Error; use crate::config::network::ConnectionParams; use crate::config::network::NetworkConfig; -use crate::Error; // Helper function to create a valid base ConnectionParams for tests fn valid_base_params() -> ConnectionParams { diff --git a/d-engine-core/src/config/tls_test.rs b/d-engine-core/src/config/tls_test.rs index 0f0029d7..3fb4ff85 100644 --- a/d-engine-core/src/config/tls_test.rs +++ b/d-engine-core/src/config/tls_test.rs @@ -1,7 +1,7 @@ use tempfile::NamedTempFile; -use crate::config::tls::TlsConfig; use crate::Error; +use crate::config::tls::TlsConfig; #[test] fn test_tls_config_default_values() { @@ -88,9 +88,11 @@ fn test_validate_self_signed_with_cert_paths_should_fail() { let error = result.unwrap_err(); assert!(matches!(error, Error::Config(_))); - assert!(error - .to_string() - .contains("Cannot specify certificate paths with generate_self_signed_certificates=true")); + assert!( + error.to_string().contains( + "Cannot specify certificate paths with generate_self_signed_certificates=true" + ) + ); } #[test] diff --git a/d-engine-core/src/test_utils/entry_builder.rs b/d-engine-core/src/test_utils/entry_builder.rs index 85f2968f..bf2eb9d1 100644 --- a/d-engine-core/src/test_utils/entry_builder.rs +++ b/d-engine-core/src/test_utils/entry_builder.rs @@ -1,8 +1,8 @@ use bytes::Bytes; -use d_engine_proto::common::membership_change::Change; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::membership_change::Change; pub struct EntryBuilder { index: u64, diff --git a/d-engine-core/src/test_utils/mock/mock_rpc.rs b/d-engine-core/src/test_utils/mock/mock_rpc.rs index a58f423c..e5c4b22f 100644 --- a/d-engine-core/src/test_utils/mock/mock_rpc.rs +++ b/d-engine-core/src/test_utils/mock/mock_rpc.rs @@ -5,11 +5,10 @@ use tonic::Streaming; use tracing::trace; use crate::test_utils::crate_test_snapshot_stream; -use d_engine_proto::client::raft_client_service_server::RaftClientService; use d_engine_proto::client::ClientReadRequest; use d_engine_proto::client::ClientResponse; use d_engine_proto::client::ClientWriteRequest; -use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementService; +use d_engine_proto::client::raft_client_service_server::RaftClientService; use d_engine_proto::server::cluster::ClusterConfChangeRequest; use d_engine_proto::server::cluster::ClusterConfUpdateResponse; use d_engine_proto::server::cluster::ClusterMembership; @@ -18,18 +17,19 @@ use d_engine_proto::server::cluster::JoinResponse; use d_engine_proto::server::cluster::LeaderDiscoveryRequest; use d_engine_proto::server::cluster::LeaderDiscoveryResponse; use d_engine_proto::server::cluster::MetadataRequest; -use d_engine_proto::server::election::raft_election_service_server::RaftElectionService; +use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementService; use d_engine_proto::server::election::VoteRequest; use d_engine_proto::server::election::VoteResponse; -use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationService; +use d_engine_proto::server::election::raft_election_service_server::RaftElectionService; use d_engine_proto::server::replication::AppendEntriesRequest; use d_engine_proto::server::replication::AppendEntriesResponse; -use d_engine_proto::server::storage::snapshot_service_server::SnapshotService; +use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationService; use d_engine_proto::server::storage::PurgeLogRequest; use d_engine_proto::server::storage::PurgeLogResponse; use d_engine_proto::server::storage::SnapshotAck; use d_engine_proto::server::storage::SnapshotChunk; use d_engine_proto::server::storage::SnapshotResponse; +use d_engine_proto::server::storage::snapshot_service_server::SnapshotService; #[derive(Clone, Default)] pub struct MockRpcService { @@ -218,7 +218,7 @@ impl SnapshotService for MockRpcService { None => { return Err(tonic::Status::unknown( "No mock install_snapshot response set", - )) + )); } } } diff --git a/d-engine-core/src/utils/file_io_test.rs b/d-engine-core/src/utils/file_io_test.rs index 5db35d38..586d15b5 100644 --- a/d-engine-core/src/utils/file_io_test.rs +++ b/d-engine-core/src/utils/file_io_test.rs @@ -1,24 +1,24 @@ use std::io::Write; use std::os::fd::AsRawFd; -use nix::libc::flock; use nix::libc::LOCK_EX; +use nix::libc::flock; use sha2::Digest; use sha2::Sha256; -use tempfile::tempdir; use tempfile::NamedTempFile; +use tempfile::tempdir; use tracing_test::traced_test; +use crate::Error; +use crate::FileError; +use crate::StorageError; +use crate::SystemError; use crate::file_io; use crate::file_io::compute_checksum_from_folder_path; use crate::file_io::convert_vec_checksum; use crate::file_io::create_parent_dir_if_not_exist; use crate::file_io::delete_file; use crate::file_io::move_directory; -use crate::Error; -use crate::FileError; -use crate::StorageError; -use crate::SystemError; /// Passed: "/tmp/files/data.txt" /// Expected: "/tmp/files" created @@ -290,8 +290,8 @@ mod validate_compressed_format_tests { use tracing::trace; use super::*; - use crate::file_io::validate_compressed_format; use crate::Result; + use crate::file_io::validate_compressed_format; /// Test valid GZIP files with supported extensions #[tokio::test] diff --git a/d-engine-core/src/utils/time_test.rs b/d-engine-core/src/utils/time_test.rs index d6e90a5f..eefc339a 100644 --- a/d-engine-core/src/utils/time_test.rs +++ b/d-engine-core/src/utils/time_test.rs @@ -1,9 +1,9 @@ use std::thread::sleep; use crate::time::get_duration_since_epoch; -use crate::time::get_now_as_u128; use crate::time::get_now_as_u32; use crate::time::get_now_as_u64; +use crate::time::get_now_as_u128; use crate::time::timestamp_millis; #[test] From 8ed8382acbd4ae61a79f85947c632ade2a11ec42 Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Thu, 30 Oct 2025 16:33:17 +0800 Subject: [PATCH 13/26] feat #167: re-export d-engine-proto --- d-engine-core/Cargo.toml | 3 +++ d-engine-core/src/election/mod.rs | 3 --- .../default_state_machine_handler_test.rs | 23 ++++--------------- .../src/test_utils/mock/mock_rpc_service.rs | 1 - d-engine-core/src/test_utils/snapshot.rs | 3 +-- d-engine-core/src/utils/cluster.rs | 5 ++-- d-engine-proto/src/exts/mod.rs | 20 ++++++++-------- d-engine-proto/src/lib.rs | 12 ++++------ 8 files changed, 25 insertions(+), 45 deletions(-) diff --git a/d-engine-core/Cargo.toml b/d-engine-core/Cargo.toml index b9d826fc..2d6e753b 100644 --- a/d-engine-core/Cargo.toml +++ b/d-engine-core/Cargo.toml @@ -52,3 +52,6 @@ temp-env = "0.3.6" tokio = { version = "1", features = ["test-util", "process"] } parking_lot = "0.12.3" nix = { version = "0.30.1", features = ["fs"] } +mockall = "0.12.1" +tonic-health = "0.12.3" +uuid = { version = "1", features = ["v4"] } diff --git a/d-engine-core/src/election/mod.rs b/d-engine-core/src/election/mod.rs index 02c01611..8094345b 100644 --- a/d-engine-core/src/election/mod.rs +++ b/d-engine-core/src/election/mod.rs @@ -12,9 +12,6 @@ pub use election_handler::*; use std::sync::Arc; -///-------------------------------------- -/// Trait Definition - #[cfg(any(test, feature = "test-utils"))] use mockall::automock; use tonic::async_trait; diff --git a/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs index 20f76696..6e216859 100644 --- a/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs +++ b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs @@ -1,4 +1,7 @@ -use std::collections::HashMap; +use bytes::Bytes; +use futures::StreamExt; +use mockall::Sequence; +use mockall::predicate::eq; use std::collections::HashSet; use std::fs; use std::net::SocketAddr; @@ -6,54 +9,36 @@ use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; - -use bytes::Bytes; -use futures::StreamExt; -use mockall::Sequence; -use mockall::predicate::eq; use tempfile::TempDir; use tempfile::tempdir; use tokio::fs::File; use tokio::fs::create_dir_all; use tokio::io::AsyncWriteExt; use tokio::sync::mpsc; -use tokio::sync::watch; -use tokio::time; use tracing::debug; use tracing_test::traced_test; use super::DefaultStateMachineHandler; -use super::MockStateMachineHandler; use super::StateMachineHandler; -use crate::AppendResults; use crate::ConsensusError; -use crate::ElectionError; use crate::Error; -use crate::MockElectionCore; use crate::MockRaftLog; -use crate::MockReplicationCore; use crate::MockSnapshotPolicy; use crate::MockStateMachine; use crate::MockTypeConfig; use crate::SnapshotError; -use crate::StateUpdate; use crate::StorageError; -use crate::test_utils::MockBuilder; use crate::test_utils::crate_test_snapshot_stream; use crate::test_utils::create_test_chunk; use crate::test_utils::create_test_compressed_snapshot; -use crate::test_utils::node_config; use crate::test_utils::snapshot_config; use d_engine_proto::common::Entry; use d_engine_proto::common::LogId; -use d_engine_proto::server::cluster::NodeMeta; -use d_engine_proto::server::election::VotedFor; use d_engine_proto::server::storage::PurgeLogRequest; use d_engine_proto::server::storage::SnapshotAck; use d_engine_proto::server::storage::SnapshotChunk; use d_engine_proto::server::storage::SnapshotMetadata; use d_engine_proto::server::storage::snapshot_ack::ChunkStatus; -use d_engine_proto::server::storage::snapshot_service_client::SnapshotServiceClient; // Case 1: normal update #[test] diff --git a/d-engine-core/src/test_utils/mock/mock_rpc_service.rs b/d-engine-core/src/test_utils/mock/mock_rpc_service.rs index 23141d17..a3e47a93 100644 --- a/d-engine-core/src/test_utils/mock/mock_rpc_service.rs +++ b/d-engine-core/src/test_utils/mock/mock_rpc_service.rs @@ -1,6 +1,5 @@ use std::net::SocketAddr; use std::sync::Arc; - use tokio::net::TcpListener; use tokio::sync::oneshot; use tonic::Status; diff --git a/d-engine-core/src/test_utils/snapshot.rs b/d-engine-core/src/test_utils/snapshot.rs index bea609bb..bfcadf0a 100644 --- a/d-engine-core/src/test_utils/snapshot.rs +++ b/d-engine-core/src/test_utils/snapshot.rs @@ -1,5 +1,3 @@ -use std::path::Path; - use bytes::BufMut; use bytes::Bytes; use bytes::BytesMut; @@ -11,6 +9,7 @@ use futures::stream::BoxStream; use http_body::Frame; use http_body_util::BodyExt; use http_body_util::StreamBody; +use std::path::Path; use tokio::fs::File; use tokio::io::AsyncWriteExt; use tokio::sync::mpsc; diff --git a/d-engine-core/src/utils/cluster.rs b/d-engine-core/src/utils/cluster.rs index ce6615a6..88ba4e2f 100644 --- a/d-engine-core/src/utils/cluster.rs +++ b/d-engine-core/src/utils/cluster.rs @@ -1,9 +1,8 @@ +use dashmap::DashSet; +use metrics::gauge; use std::sync::Arc; use std::time::SystemTime; use std::time::UNIX_EPOCH; - -use dashmap::DashSet; -use metrics::gauge; use tracing::error; use d_engine_proto::common::Entry; diff --git a/d-engine-proto/src/exts/mod.rs b/d-engine-proto/src/exts/mod.rs index 8a2a5ccc..85fb2731 100644 --- a/d-engine-proto/src/exts/mod.rs +++ b/d-engine-proto/src/exts/mod.rs @@ -1,11 +1,11 @@ -mod client_ext; -mod cluster_ext; -mod common_ext; -mod replication_ext; -mod storage_ext; +//! Protocol buffer type extensions +//! +//! This module provides additional trait implementations and helper methods +//! for protobuf-generated types. While the imports appear unused at the module level, +//! they are re-exported for use by dependent crates (d-engine-core, d-engine-runtime, d-engine-client). -pub use client_ext::*; -pub use cluster_ext::*; -pub use common_ext::*; -pub use replication_ext::*; -pub use storage_ext::*; +pub mod client_ext; +pub mod cluster_ext; +pub mod common_ext; +pub mod replication_ext; +pub mod storage_ext; diff --git a/d-engine-proto/src/lib.rs b/d-engine-proto/src/lib.rs index 86d90e8f..09f0b170 100644 --- a/d-engine-proto/src/lib.rs +++ b/d-engine-proto/src/lib.rs @@ -1,7 +1,8 @@ -//! Protocol Buffer definitions and generated code for RPC services. +//! Protocol buffer type extensions //! -//! This module contains auto-generated Rust types from Protobuf definitions, -//! typically created using [`tonic-build`] or `protoc` compiler plugins. +//! This module provides additional trait implementations and helper methods +//! for protobuf-generated types. The re-exports here are used by dependent crates +//! (d-engine-core, d-engine-runtime, d-engine-client) when they import from this crate. pub mod common { include!("generated/d_engine.common.rs"); @@ -32,7 +33,4 @@ pub mod client { include!("generated/d_engine.client.rs"); } -mod exts; - -#[doc(hidden)] -pub use exts::*; +pub mod exts; From 5311e3e7fba6812f6ea01f1579d239a95353a9c3 Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Thu, 30 Oct 2025 17:55:55 +0800 Subject: [PATCH 14/26] feat #167: Makefile optimization --- Makefile | 432 +++++++++++++++--- clippy.toml | 200 ++++++++ d-engine-core/src/utils/stream.rs | 6 + .../src/membership/raft_membership.rs | 1 - .../raft_role/learner_state_test.rs | 4 +- 5 files changed, 580 insertions(+), 63 deletions(-) create mode 100644 clippy.toml diff --git a/Makefile b/Makefile index e6a90e97..4c3b3164 100644 --- a/Makefile +++ b/Makefile @@ -1,71 +1,383 @@ -# Release Management Makefile -.PHONY: all pre-release check-env install clean help +# ============================================================================ +# Makefile for d-engine Rust Workspace +# ============================================================================ +# This Makefile provides convenient commands for managing a multi-crate +# Rust workspace including: formatting, linting, testing, and release tasks. +# +# Usage: make +# Examples: +# make help # Show all available targets +# make check # Run all quality checks (fmt, clippy, test) +# make fmt-fix # Fix all formatting issues automatically +# make clippy-fix # Apply clippy suggestions automatically +# make test # Run all tests with logging +# make pre-release # Comprehensive pre-release validation +# +# ============================================================================ -# Configuration -SCRIPT_PATH := scripts/pre-release-checklist.sh -CARGO_TOOLCHAIN := 1.84.0 # Update to your toolchain -CRATE_NAME := d_engine # Update to your crate name -RUST_LOG_LEVEL := d_engine=info +.PHONY: help all check fmt fmt-check clippy clippy-fix test test-unit \ + test-integration test-doc bench clean doc build build-release \ + pre-release install check-env audit deny troubleshoot -## Show this help +# ============================================================================ +# CONFIGURATION SECTION +# ============================================================================ + +# Cargo executable +CARGO ?= cargo + +# Rust logging level for tests +RUST_LOG_LEVEL ?= d_engine=debug + +# Backtrace level for debugging +RUST_BACKTRACE ?= 1 + +# Workspace member crates +WORKSPACE_MEMBERS := d-engine-proto d-engine-core d-engine-client d-engine-runtime d-engine-docs + +# Color codes for formatted output +RED := \033[0;31m +GREEN := \033[0;32m +YELLOW := \033[1;33m +BLUE := \033[0;34m +MAGENTA := \033[0;35m +CYAN := \033[0;36m +NC := \033[0m # No Color + +# ============================================================================ +# HELP TARGET - Display all available commands +# ============================================================================ + +## help Display this help message with all available targets help: - @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) - -all: pre-release - -## Run all pre-release checks -pre-release: check-env install ## Run all pre-release checks - @echo "\n\033[1;33m=== Starting Release Preparation ===\033[0m" - @$(SCRIPT_PATH) || (echo "\033[0;31mPre-release checks failed!\033[0m" && exit 1) - @echo "\n\033[1;32m✓ All checks passed. Ready for release!\033[0m" - -## Verify environment prerequisites -check-env: ## Verify environment - @command -v cargo >/dev/null 2>&1 || \ - (echo "\033[0;31mError: cargo not found!\033[0m" && exit 1) - @rustup toolchain list | grep -q $(CARGO_TOOLCHAIN) || \ - (echo "\033[0;31mError: Required toolchain $(CARGO_TOOLCHAIN) missing!\033[0m" && exit 1) - -## Install prerequisites -install: ## Install prerequisites - @chmod +x $(SCRIPT_PATH) - @rustup component add \ - clippy \ - rustfmt \ - rust-docs - -## Clean build artifacts -clean: ## Clean build artifacts - @cargo clean + @echo "$(BLUE)╔════════════════════════════════════════════════════════╗$(NC)" + @echo "$(BLUE)║ d-engine Workspace Build System ║$(NC)" + @echo "$(BLUE)╚════════════════════════════════════════════════════════╝$(NC)" + @echo "" + @echo "$(CYAN)Available Targets:$(NC)" + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " $(GREEN)%-20s$(NC) %s\n", $$1, $$2}' $(MAKEFILE_LIST) + @echo "" + @echo "$(CYAN)Common Workflows:$(NC)" + @echo " $(YELLOW)Development:$(NC)" + @echo " make check # Run all quality checks before committing" + @echo " make fmt-fix # Auto-fix formatting issues" + @echo " make clippy-fix # Apply clippy suggestions" + @echo "" + @echo " $(YELLOW)Testing:$(NC)" + @echo " make test # Run unit + integration tests" + @echo " make test-all # Run all tests including benchmarks" + @echo " make test-unit # Run unit tests only" + @echo "" + @echo " $(YELLOW)Release:$(NC)" + @echo " make pre-release # Full pre-release validation" + @echo " make build-release # Build release artifacts" + @echo "" + @echo " $(YELLOW)Maintenance:$(NC)" + @echo " make clean # Remove all build artifacts" + @echo " make audit # Check for security vulnerabilities" + @echo " make troubleshoot # Diagnostic information" + @echo "" + +# ============================================================================ +# ENVIRONMENT VERIFICATION +# ============================================================================ + +## check-env Verify Rust environment prerequisites are installed +check-env: + @echo "$(BLUE)Verifying Rust environment...$(NC)" + @command -v $(CARGO) >/dev/null 2>&1 || \ + { echo "$(RED)✗ Error: cargo not found in PATH$(NC)"; exit 1; } + @command -v rustc >/dev/null 2>&1 || \ + { echo "$(RED)✗ Error: rustc not found in PATH$(NC)"; exit 1; } + @command -v rustup >/dev/null 2>&1 || \ + { echo "$(RED)✗ Error: rustup not found in PATH$(NC)"; exit 1; } + @echo "$(GREEN)✓ Cargo found: $$($(CARGO) --version)$(NC)" + @echo "$(GREEN)✓ Rustc found: $$(rustc --version)$(NC)" + +## install-tools Install required Rust components (rustfmt, clippy, etc.) +install-tools: check-env + @echo "$(BLUE)Installing Rust development components...$(NC)" + @rustup component add rustfmt clippy rust-src rust-analyzer 2>/dev/null || true + @echo "$(GREEN)✓ Components installed$(NC)" + @echo "$(CYAN)Optional: cargo install cargo-nextest --locked$(NC)" + +## install Alias for install-tools +install: install-tools + +# ============================================================================ +# WORKSPACE VALIDATION +# ============================================================================ + +## check-workspace Verify all workspace crates are properly configured +check-workspace: check-env + @echo "$(BLUE)Validating workspace structure...$(NC)" + @for member in $(WORKSPACE_MEMBERS); do \ + if [ ! -f "$$member/Cargo.toml" ]; then \ + echo "$(RED)✗ Missing Cargo.toml in $$member$(NC)"; \ + exit 1; \ + fi; \ + echo "$(GREEN)✓ $$member/Cargo.toml found$(NC)"; \ + done + @echo "$(GREEN)✓ Workspace structure validated$(NC)" + +# ============================================================================ +# CODE QUALITY - FORMATTING +# ============================================================================ + +## fmt-check Check code formatting without modifying files (dry-run) +fmt-check: check-workspace + @echo "$(BLUE)Checking code formatting...$(NC)" + @$(CARGO) fmt --all -- --check || \ + { echo "$(RED)✗ Formatting issues found. Run 'make fmt-fix' to fix them.$(NC)"; exit 1; } + @echo "$(GREEN)✓ Code formatting check passed$(NC)" + +## fmt fmt-fix Fix all code formatting issues automatically +fmt fmt-fix: install-tools check-workspace + @echo "$(BLUE)Applying code formatting fixes...$(NC)" + @$(CARGO) fmt --all + @echo "$(GREEN)✓ Code formatting fixed in all crates$(NC)" + +# ============================================================================ +# CODE QUALITY - LINTING (CLIPPY) +# ============================================================================ + +## clippy Run Clippy linter (fail on warnings with -D flag) +clippy: check-workspace + @echo "$(BLUE)Running Clippy linter on all crates...$(NC)" + @$(CARGO) clippy --workspace --all-targets --all-features -- -D warnings || \ + { echo "$(RED)✗ Clippy warnings found. Run 'make clippy-fix' for suggestions.$(NC)"; exit 1; } + @echo "$(GREEN)✓ Clippy lint check passed$(NC)" + +## clippy-fix Automatically apply Clippy suggestions (review changes!) +clippy-fix: install-tools check-workspace + @echo "$(BLUE)Applying Clippy suggestions...$(NC)" + @$(CARGO) clippy --workspace --all-targets --all-features --fix \ + --allow-no-vcs --allow-dirty --allow-staged + @echo "$(YELLOW)Re-checking for remaining issues...$(NC)" + @$(CARGO) clippy --workspace --all-targets --all-features -- -D warnings || \ + { echo "$(YELLOW)Note: Some warnings require manual review and fixes$(NC)"; } + @echo "$(GREEN)✓ Clippy fixes applied$(NC)" + @echo "$(MAGENTA)IMPORTANT: Review changes before committing!$(NC)" + +# ============================================================================ +# CODE QUALITY - COMPREHENSIVE CHECKS +# ============================================================================ + +## check Run all quality checks (fmt, clippy, cargo check) +check: fmt-check clippy + @echo "" + @echo "$(GREEN)╔════════════════════════════════════════╗$(NC)" + @echo "$(GREEN)║ ✓ All code quality checks passed! ║$(NC)" + @echo "$(GREEN)╚════════════════════════════════════════╝$(NC)" + +## quick-check Fast validation (cargo check + fmt-check only) +quick-check: check-workspace + @echo "$(BLUE)Running quick validation...$(NC)" + @$(CARGO) check --workspace --all-targets --all-features + @$(CARGO) fmt --all -- --check || exit 1 + @echo "$(GREEN)✓ Quick validation passed$(NC)" + +# ============================================================================ +# BUILD TARGETS +# ============================================================================ + +## build Build all workspace crates in debug mode +build: check-workspace + @echo "$(BLUE)Building workspace (debug mode)...$(NC)" + @$(CARGO) build --workspace --all-targets --all-features + @echo "$(GREEN)✓ Debug build completed$(NC)" + +## build-release Build all workspace crates in release mode (optimized) +build-release: check check-workspace + @echo "$(BLUE)Building workspace (release mode, optimized)...$(NC)" + @$(CARGO) build --workspace --all-features --release + @echo "$(GREEN)✓ Release build completed$(NC)" + @echo "$(CYAN)Release artifacts: target/release/$(NC)" + +# ============================================================================ +# TESTING +# ============================================================================ + +## test Run unit and integration tests (with logging) +test: install-tools check-workspace + @echo "$(BLUE)Running unit and integration tests...$(NC)" + @RUST_LOG=$(RUST_LOG_LEVEL) RUST_BACKTRACE=$(RUST_BACKTRACE) \ + $(CARGO) test --workspace --lib --all-targets --no-fail-fast -- --test-threads=1 --nocapture + @echo "$(BLUE)Running integration tests...$(NC)" + @RUST_LOG=$(RUST_LOG_LEVEL) RUST_BACKTRACE=$(RUST_BACKTRACE) \ + $(CARGO) test --workspace --test '*' --no-fail-fast -- --nocapture + @echo "$(GREEN)✓ All tests passed$(NC)" + +## test-unit Run unit tests only (--lib flag) +test-unit: install-tools check-workspace + @echo "$(BLUE)Running unit tests...$(NC)" + @RUST_LOG=$(RUST_LOG_LEVEL) RUST_BACKTRACE=$(RUST_BACKTRACE) \ + $(CARGO) test --workspace --lib --no-fail-fast -- --nocapture + @echo "$(GREEN)✓ Unit tests passed$(NC)" + +## test-integration Run integration tests only (--test flag) +test-integration: install-tools check-workspace + @echo "$(BLUE)Running integration tests...$(NC)" + @RUST_LOG=$(RUST_LOG_LEVEL) RUST_BACKTRACE=$(RUST_BACKTRACE) \ + $(CARGO) test --workspace --test '*' --no-fail-fast -- --nocapture + @echo "$(GREEN)✓ Integration tests passed$(NC)" + +## test-doc Run documentation tests only +test-doc: install-tools check-workspace + @echo "$(BLUE)Running documentation tests...$(NC)" + @$(CARGO) test --doc --workspace + @echo "$(GREEN)✓ Documentation tests passed$(NC)" + +## test-all Run all tests: unit + integration + doc + benchmarks +test-all: test test-doc bench + @echo "$(GREEN)✓ All test suites passed$(NC)" + +## test-verbose Run tests with verbose output and single-threaded execution +test-verbose: install-tools check-workspace + @echo "$(BLUE)Running tests (verbose, single-threaded)...$(NC)" + @RUST_LOG=$(RUST_LOG_LEVEL) RUST_BACKTRACE=$(RUST_BACKTRACE) \ + $(CARGO) test --workspace -- --nocapture --test-threads=1 + @echo "$(GREEN)✓ Verbose test run completed$(NC)" + +# ============================================================================ +# PERFORMANCE & BENCHMARKING +# ============================================================================ + +## bench Run performance benchmarks (if configured) +bench: check-workspace + @echo "$(BLUE)Running performance benchmarks...$(NC)" + @$(CARGO) bench --workspace --all-features --no-fail-fast -- --nocapture || \ + { echo "$(YELLOW)Note: No benchmarks configured or not supported in this workspace$(NC)"; } + @echo "$(GREEN)✓ Benchmark run completed$(NC)" + +# ============================================================================ +# DOCUMENTATION +# ============================================================================ + +## doc Generate API documentation and open in browser +doc: check-workspace + @echo "$(BLUE)Generating API documentation...$(NC)" + @$(CARGO) doc --workspace --all-features --no-deps --open + @echo "$(GREEN)✓ Documentation generated and opened$(NC)" + +## doc-check Generate documentation without opening browser +doc-check: check-workspace + @echo "$(BLUE)Checking documentation generation...$(NC)" + @$(CARGO) doc --workspace --all-features --no-deps + @echo "$(GREEN)✓ Documentation generated successfully$(NC)" + +# ============================================================================ +# SECURITY & DEPENDENCY AUDITING +# ============================================================================ + +## audit Audit dependencies for known security vulnerabilities +audit: check-env + @echo "$(BLUE)Auditing dependencies for vulnerabilities...$(NC)" + @if command -v cargo-audit >/dev/null 2>&1; then \ + cargo audit; \ + else \ + echo "$(YELLOW)Note: cargo-audit not installed. Install with: cargo install cargo-audit$(NC)"; \ + fi + +## deny Run cargo deny for supply chain security checks +deny: check-env + @echo "$(BLUE)Running cargo deny checks...$(NC)" + @if command -v cargo-deny >/dev/null 2>&1; then \ + cargo deny check; \ + else \ + echo "$(YELLOW)Note: cargo-deny not installed. Install with: cargo install cargo-deny$(NC)"; \ + fi + +# ============================================================================ +# CLEANUP & MAINTENANCE +# ============================================================================ + +## clean Remove all build artifacts and cache files +clean: + @echo "$(BLUE)Cleaning build artifacts...$(NC)" + @$(CARGO) clean @rm -rf target/ coverage/ *.log + @echo "$(GREEN)✓ Cleanup completed$(NC)" + +## clean-deps Aggressive cleanup: remove artifacts, cache, and Cargo.lock +clean-deps: clean + @echo "$(BLUE)Aggressive dependency cleanup...$(NC)" + @rm -f Cargo.lock + @echo "$(YELLOW)Updating Rust toolchain...$(NC)" + @rustup self update + @echo "$(GREEN)✓ Deep cleanup completed$(NC)" + +# ============================================================================ +# DIAGNOSTIC & TROUBLESHOOTING +# ============================================================================ -# Benchmark Targets (Optional) -bench: ## Run performance benchmarks - @cargo bench --features unstable +## troubleshoot Display diagnostic information for debugging build issues +troubleshoot: check-env + @echo "$(MAGENTA)═══════════════════════════════════════════════════════$(NC)" + @echo "$(MAGENTA) DIAGNOSTIC INFORMATION FOR TROUBLESHOOTING$(NC)" + @echo "$(MAGENTA)═══════════════════════════════════════════════════════$(NC)" + @echo "" + @echo "$(CYAN)1. Rust Environment:$(NC)" + @echo " Rustc version:" + @rustc --version + @echo " Cargo version:" + @$(CARGO) --version + @echo " Toolchain info:" + @rustup show + @echo "" + @echo "$(CYAN)2. Workspace Metadata:$(NC)" + @$(CARGO) metadata --format-version=1 | grep -E '"name"|"version"' | head -20 + @echo "" + @echo "$(CYAN)3. Workspace Members:$(NC)" + @for member in $(WORKSPACE_MEMBERS); do \ + if [ -f "$$member/Cargo.toml" ]; then \ + echo " ✓ $$member"; \ + else \ + echo " ✗ $$member (missing)"; \ + fi; \ + done + @echo "" + @echo "$(CYAN)4. Disk Usage:$(NC)" + @du -sh target/ 2>/dev/null || echo " target/ not found" + @du -sh . | tail -1 || echo " Unable to calculate" + @echo "" + @echo "$(CYAN)5. Common Issues & Fixes:$(NC)" + @echo " • Compilation slow: Try 'make clean' and rebuild" + @echo " • Tests timeout: Check 'make test-verbose' output" + @echo " • Clippy errors: Run 'make clippy-fix' for automatic fixes" + @echo " • Format issues: Run 'make fmt-fix' to auto-format" + @echo "" -# Documentation Targets -doc: ## Generate documentation - @cargo doc --all-features --no-deps --open +# ============================================================================ +# COMPOSITE WORKFLOWS +# ============================================================================ -# Format Verification -fmt-check: ## Check formatting - @cargo fmt --all -- --check +## pre-release Full pre-release validation (comprehensive checks) +pre-release: install-tools check-workspace check test-all audit build-release + @echo "" + @echo "$(GREEN)╔═══════════════════════════════════════════════════════╗$(NC)" + @echo "$(GREEN)║ ✓ ALL PRE-RELEASE CHECKS PASSED SUCCESSFULLY! ║$(NC)" + @echo "$(GREEN)╚═══════════════════════════════════════════════════════╝$(NC)" + @echo "" + @echo "$(MAGENTA)Next Steps:$(NC)" + @echo " 1. Review CHANGELOG.md for accuracy" + @echo " 2. Update version numbers in all Cargo.toml files" + @echo " 3. Create annotated git tag: git tag -a v" + @echo " 4. Push to repository: git push && git push --tags" + @echo "" -# Format Verification -fmt-fix: ## Check formatting - @cargo fmt --all +## fix Run automatic fixes (fmt-fix + clippy-fix) +fix: fmt-fix clippy-fix + @echo "" + @echo "$(GREEN)✓ Automatic fixes completed$(NC)" + @echo "$(MAGENTA)IMPORTANT: Review and test your changes before committing!$(NC)" -# Run Clippy -clippy: ## Run Clippy - @cargo clippy --all-targets --all-features -- -D warnings +## all Default target: run full check suite +all: check + @echo "$(GREEN)✓ Check suite completed$(NC)" -# Run Clippy fix -clippy-fix: ## Run Clippy fix - @cargo clippy --fix +# ============================================================================ +# DEFAULT TARGET +# ============================================================================ -# Test suite -test: ## Run test suite - @RUST_LOG=$(RUST_LOG_LEVEL) cargo nextest run --no-fail-fast --tests --nocapture - @RUST_LOG=$(RUST_LOG_LEVEL) cargo nextest run --no-fail-fast --lib --nocapture - @RUST_LOG=$(RUST_LOG_LEVEL) cargo test --doc - @RUST_LOG=$(RUST_LOG_LEVEL) cargo bench -- --nocapture +.DEFAULT_GOAL := help diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000..cad9ec90 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,200 @@ +# ============================================================================ +# Clippy Configuration for d-engine Workspace +# ============================================================================ +# This file configures Clippy linting rules for the entire workspace. +# +# Documentation: https://rust-lang.github.io/rust-clippy/master/index.html +# Configuration Reference: https://doc.rust-lang.org/clippy/configuration.html +# +# All workspace crates inherit these settings unless explicitly overridden +# in crate-specific attributes (e.g., #[allow(clippy::foo)]). +# ============================================================================ + +# ============================================================================ +# Complexity and Performance Thresholds +# ============================================================================ + +# Maximum cognitive complexity threshold for functions +# Default: 25 +# Functions exceeding this complexity will trigger cognitive_complexity warning +cognitive-complexity-threshold = 30 + +# Maximum number of function arguments before suggesting refactoring +# Default: 7 +# Functions with more parameters than this will trigger too_many_arguments +too-many-arguments-threshold = 8 + +# Maximum number of lines in a function body +# Default: 200 +# This helps identify functions that might be too large to understand easily +too-many-lines-threshold = 250 + +# Type complexity threshold +# Default: 250 +# Complex generic types exceeding this threshold trigger type_complexity warning +type-complexity-threshold = 300 + +# ============================================================================ +# Code Style and Formatting Preferences +# ============================================================================ + +# Enforce explicit documentation on public API items +# Set to true to require docs on all public items, modules, and functions +# Default: false +# For strict documentation standards, set to true +missing-docs-in-crate-items = false + +# Enforce parentheses around complex expressions +# Default: false +# Set to true for more explicit parentheses +enforce-iter-loop-reborrow = false + + +# ============================================================================ +# Pattern-Specific Configuration +# ============================================================================ + +# Wildcard imports threshold +# Default: None (allow all wildcard imports) +# wildcard-imports-allowed-crates = ["log", "sqlx::prelude"] + +# Suppress warnings for specific lint patterns +# This is useful for third-party code or when you know what you're doing +# allowed-scripts = ["Latin"] + +# ============================================================================ +# Documentation and Comments +# ============================================================================ + +# Enforce documentation for public items in crates +# Default: false +# Set to true to require /// doc comments on public functions +doc-valid-idents = [ + # Standard Rust identifiers + "Raft", + "Tokio", + "RocksDB", + "TCP", + "UDP", + "HTTP", + "HTTPS", + "API", + "UUID", + "URL", + "UTF-8", + "CRC", + "SHA", + "LRU", + "LTO", + "LLVM", + "JIT", + "EOF", + "YAML", + "JSON", + "XML", + "ASCII", + "ANSI", + # Project-specific identifiers + "DEngine", + "d-engine", + "EventLab", + "Consensus", + "Replication", + "Quorum", + "Term", + "Index", +] + +# Character name length thresholds +single-char-binding-names-threshold = 4 + +# ============================================================================ +# Safety and Correctness +# ============================================================================ + +# Restrict unsafe code to specific modules (optional) +# When set, unsafe code outside specified modules triggers warnings +# unsafe-derive-serialize = false + +# Check for potential panics +# When true, warns about patterns that might panic +# allow-panic = false + +# ============================================================================ +# Testing and Development +# ============================================================================ + +# Allow certain patterns in test code that would normally be warnings +# Tests often use patterns that are acceptable in that context +allow-unwrap-in-tests = true +allow-expect-in-tests = true + +# ============================================================================ +# Import Organization +# ============================================================================ + +# Enforce wildcard imports are only from specific crates +# Helps maintain clean imports +# wildcard-imports-allowed-crates = [] + +# ============================================================================ +# Lint Override Examples (Commented Out) +# ============================================================================ +# The following are examples of how to suppress specific lints. +# Uncomment if needed for your project. + +# # Suppress warnings for specific patterns: +# # (In code, use #[allow(clippy::lint_name)]) +# # Example: #[allow(clippy::too_many_arguments)] + +# ============================================================================ +# Best Practices & Tips +# ============================================================================ +# +# 1. COGNITIVE COMPLEXITY (default: 25) +# - Functions with high cognitive complexity are harder to understand +# - Consider breaking them into smaller helper functions +# - Threshold of 30 allows for moderately complex functions +# +# 2. TOO MANY ARGUMENTS (default: 7) +# - Functions with many parameters are hard to call and maintain +# - Consider grouping related parameters into a struct +# - Threshold of 8 allows for functions with moderate parameter counts +# +# 3. TYPE COMPLEXITY (default: 250) +# - Complex generic types are hard to understand +# - Consider type aliases: type MyType = Vec>>; +# +# 4. DOCUMENTATION (default: off) +# - Set missing_docs_in_crate_items = true for well-documented public APIs +# - Use //! for module-level documentation +# - Use /// for item-level documentation +# +# 5. UNSAFE CODE +# - Always document why unsafe is necessary +# - Use #[allow(unsafe_code)] with comments explaining safety invariants +# +# 6. TESTING +# - allow_unwrap_in_tests = true is recommended for cleaner test code +# - Test code has different style requirements than production code +# +# ============================================================================ +# Common Clippy Lints to Be Aware Of +# ============================================================================ +# +# HIGH PRIORITY (usually should be fixed): +# - clippy::all: Enables all lint groups +# - clippy::correctness: Correctness issues that are bugs +# - clippy::perf: Performance anti-patterns +# - clippy::style: Code style issues +# +# MEDIUM PRIORITY (should usually be fixed): +# - clippy::pedantic: More aggressive lints, higher false positive rate +# - clippy::suspicious: Suspicious patterns that might be bugs +# +# LOW PRIORITY (contextual): +# - clippy::cargo: Build system specific lints +# - clippy::complexity: Unnecessarily complex code +# - clippy::nursery: Experimental lints not yet stable +# +# ============================================================================ diff --git a/d-engine-core/src/utils/stream.rs b/d-engine-core/src/utils/stream.rs index 86deffc8..2a8bce53 100644 --- a/d-engine-core/src/utils/stream.rs +++ b/d-engine-core/src/utils/stream.rs @@ -46,6 +46,12 @@ pub fn encode_varint( buf.put_u8(value as u8); } +impl Default for GrpcStreamDecoder { + fn default() -> Self { + Self::new() + } +} + impl GrpcStreamDecoder { pub fn new() -> Self { GrpcStreamDecoder { diff --git a/d-engine-runtime/src/membership/raft_membership.rs b/d-engine-runtime/src/membership/raft_membership.rs index a877ab21..7534356e 100644 --- a/d-engine-runtime/src/membership/raft_membership.rs +++ b/d-engine-runtime/src/membership/raft_membership.rs @@ -25,7 +25,6 @@ use tonic::transport::Channel; use tracing::debug; use tracing::error; use tracing::info; -use tracing::instrument; use tracing::warn; use super::MembershipGuard; diff --git a/d-engine-runtime/tests/components/raft_role/learner_state_test.rs b/d-engine-runtime/tests/components/raft_role/learner_state_test.rs index 6a48f31a..4a50947f 100644 --- a/d-engine-runtime/tests/components/raft_role/learner_state_test.rs +++ b/d-engine-runtime/tests/components/raft_role/learner_state_test.rs @@ -626,9 +626,9 @@ fn mock_context( ) -> RaftContext { let temp_dir = tempfile::tempdir().unwrap(); let case_path = temp_dir.path().join(case_name); - let raft_context = mock_raft_context(case_path.to_str().unwrap(), shutdown_signal, None); + - raft_context + mock_raft_context(case_path.to_str().unwrap(), shutdown_signal, None) } /// Tests leader selection with multiple valid responses From 7d50aaa2d3a23e491ce82f4d493e42e3e3db7c05 Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Thu, 30 Oct 2025 20:33:34 +0800 Subject: [PATCH 15/26] feat #167: fix clippy --- d-engine-client/src/proto/client_ext.rs | 1 + d-engine-core/src/maybe_clone_oneshot.rs | 2 + .../backgroup_snapshot_transfer_test.rs | 31 +++- .../default_state_machine_handler_test.rs | 7 +- .../src/test_utils/mock/mock_rpc_service.rs | 20 +-- d-engine-core/src/test_utils/snapshot.rs | 27 --- d-engine-core/src/utils/cluster.rs | 37 ---- d-engine-core/src/utils/convert.rs | 12 -- d-engine-core/src/utils/convert_test.rs | 38 ---- d-engine-core/src/utils/file_io.rs | 92 ++-------- d-engine-core/src/utils/file_io_test.rs | 163 ------------------ .../src/membership/raft_membership_test.rs | 14 +- .../src/network/connection_cache_test.rs | 7 +- .../src/network/grpc/grpc_transport_test.rs | 2 +- .../src/network/health_checker_test.rs | 19 +- .../src/storage/buffered/buffered_raft_log.rs | 10 ++ d-engine-runtime/src/test_utils/mock/mod.rs | 4 +- 17 files changed, 97 insertions(+), 389 deletions(-) diff --git a/d-engine-client/src/proto/client_ext.rs b/d-engine-client/src/proto/client_ext.rs index 5da2bc83..701bf2d4 100644 --- a/d-engine-client/src/proto/client_ext.rs +++ b/d-engine-client/src/proto/client_ext.rs @@ -12,6 +12,7 @@ pub trait ClientResponseExt { /// # Returns /// - `Ok(true)` on successful write /// - `Err` with converted error code on failure + #[allow(dead_code)] fn into_write_result(self) -> std::result::Result; /// Convert response to read results diff --git a/d-engine-core/src/maybe_clone_oneshot.rs b/d-engine-core/src/maybe_clone_oneshot.rs index 21e898b8..921a2f8d 100644 --- a/d-engine-core/src/maybe_clone_oneshot.rs +++ b/d-engine-core/src/maybe_clone_oneshot.rs @@ -34,6 +34,7 @@ pub trait RaftOneshot { pub struct MaybeCloneOneshot; pub struct MaybeCloneOneshotSender { + #[allow(dead_code)] inner: oneshot::Sender, #[cfg(any(test, feature = "test-utils"))] @@ -50,6 +51,7 @@ impl Debug for MaybeCloneOneshotSender { } pub struct MaybeCloneOneshotReceiver { + #[allow(dead_code)] inner: oneshot::Receiver, #[cfg(any(test, feature = "test-utils"))] diff --git a/d-engine-core/src/network/backgroup_snapshot_transfer_test.rs b/d-engine-core/src/network/backgroup_snapshot_transfer_test.rs index 74b6c67b..8f62d953 100644 --- a/d-engine-core/src/network/backgroup_snapshot_transfer_test.rs +++ b/d-engine-core/src/network/backgroup_snapshot_transfer_test.rs @@ -3,6 +3,7 @@ use std::time::Duration; use bytes::Bytes; use futures::StreamExt; use futures::stream; +use futures::stream::BoxStream; use tokio::sync::oneshot; use tokio::time::Instant; use tonic::Status; @@ -15,11 +16,35 @@ use crate::NetworkError; use crate::SnapshotConfig; use crate::SnapshotError; use crate::StorageError; -use crate::test_utils::MockNode; -use crate::test_utils::create_snapshot_stream; +use crate::crate_test_snapshot_stream; +use crate::create_test_chunk; use d_engine_proto::server::storage::SnapshotChunk; use d_engine_proto::server::storage::SnapshotResponse; +/// Helper to create a valid snapshot stream +fn create_snapshot_stream( + chunks: usize, + chunk_size: usize, +) -> BoxStream<'static, Result> { + let chunks: Vec = (0..chunks) + .map(|seq| { + let data = vec![seq as u8; chunk_size]; + create_test_chunk( + seq as u32, + &data, + 1, // term + 1, // leader_id + chunks as u32, + ) + }) + .collect(); + + let stream = crate_test_snapshot_stream(chunks); + Box::pin( + stream.map(|item| item.map_err(|s| NetworkError::TonicStatusError(Box::new(s)).into())), + ) +} + fn default_snapshot_config() -> SnapshotConfig { SnapshotConfig { max_bandwidth_mbps: 1, @@ -53,7 +78,7 @@ fn create_snapshot_chunk( #[cfg(test)] mod run_push_transfer_test { use super::*; - use crate::SystemError; + use crate::{MockNode, SystemError}; #[tokio::test] async fn test_push_transfer_success() { diff --git a/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs index 6e216859..9c5d257b 100644 --- a/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs +++ b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs @@ -4,7 +4,6 @@ use mockall::Sequence; use mockall::predicate::eq; use std::collections::HashSet; use std::fs; -use std::net::SocketAddr; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -308,9 +307,9 @@ mod apply_chunk_test { } } -fn listen_addr(port: u32) -> SocketAddr { - format!("127.0.0.1:{port}",).parse().unwrap() -} +// fn listen_addr(port: u32) -> SocketAddr { +// format!("127.0.0.1:{port}",).parse().unwrap() +// } // /// Case1: Complete successful snapshot installation // #[tokio::test] diff --git a/d-engine-core/src/test_utils/mock/mock_rpc_service.rs b/d-engine-core/src/test_utils/mock/mock_rpc_service.rs index a3e47a93..3615f7b2 100644 --- a/d-engine-core/src/test_utils/mock/mock_rpc_service.rs +++ b/d-engine-core/src/test_utils/mock/mock_rpc_service.rs @@ -112,7 +112,7 @@ impl MockNode { } // Update helper functions to handle dynamic ports - pub(crate) async fn mock_channel_with_port(port: u16) -> Channel { + pub async fn mock_channel_with_port(port: u16) -> Channel { Channel::from_shared(format!("http://127.0.0.1:{port}")) .expect("valid address") .connect() @@ -120,11 +120,11 @@ impl MockNode { .expect("connection failed") } - pub(crate) fn tcp_addr_to_http_addr(addr: String) -> String { + pub fn tcp_addr_to_http_addr(addr: String) -> String { format!("http://{addr}") } - pub(crate) async fn simulate_send_votes_mock_server( + pub async fn simulate_send_votes_mock_server( response: VoteResponse, rx: oneshot::Receiver<()>, ) -> Result<(Channel, u16)> { @@ -139,7 +139,7 @@ impl MockNode { Ok((channel, port)) } - pub(crate) async fn simulate_purge_mock_server( + pub async fn simulate_purge_mock_server( response: PurgeLogResponse, rx: oneshot::Receiver<()>, ) -> Result<(Channel, u16)> { @@ -155,7 +155,7 @@ impl MockNode { } // Return channel + port - pub(crate) async fn simulate_snapshot_mock_server( + pub async fn simulate_snapshot_mock_server( response: std::result::Result, rx: oneshot::Receiver<()>, ) -> Result<(Channel, u16)> { @@ -170,7 +170,7 @@ impl MockNode { } #[allow(clippy::type_complexity)] - pub(crate) async fn simulate_mock_service_with_cluster_conf_reps( + pub async fn simulate_mock_service_with_cluster_conf_reps( rx: oneshot::Receiver<()>, response_builder: Option< Box std::result::Result + Send + Sync>, @@ -197,7 +197,7 @@ impl MockNode { Ok((channel, port)) } - pub(crate) async fn simulate_append_entries_mock_server( + pub async fn simulate_append_entries_mock_server( response: std::result::Result, rx: oneshot::Receiver<()>, ) -> Result<(Channel, u16)> { @@ -212,7 +212,7 @@ impl MockNode { } #[allow(clippy::type_complexity)] - pub(crate) async fn simulate_mock_service_with_join_cluster_reps( + pub async fn simulate_mock_service_with_join_cluster_reps( rx: oneshot::Receiver<()>, metadata_response_builder: Option< Box std::result::Result + Send + Sync>, @@ -243,7 +243,7 @@ impl MockNode { } #[allow(clippy::type_complexity)] - pub(crate) async fn simulate_client_read_mock_server( + pub async fn simulate_client_read_mock_server( rx: oneshot::Receiver<()>, metadata_response_builder: Option< Box std::result::Result + Send + Sync>, @@ -274,7 +274,7 @@ impl MockNode { } #[allow(clippy::type_complexity)] - pub(crate) async fn simulate_client_write_mock_server( + pub async fn simulate_client_write_mock_server( rx: oneshot::Receiver<()>, metadata_response_builder: Option< Box std::result::Result + Send + Sync>, diff --git a/d-engine-core/src/test_utils/snapshot.rs b/d-engine-core/src/test_utils/snapshot.rs index bfcadf0a..1cd5c3c1 100644 --- a/d-engine-core/src/test_utils/snapshot.rs +++ b/d-engine-core/src/test_utils/snapshot.rs @@ -5,7 +5,6 @@ use crc32fast::Hasher; use futures::StreamExt; use futures::TryStreamExt; use futures::stream; -use futures::stream::BoxStream; use http_body::Frame; use http_body_util::BodyExt; use http_body_util::StreamBody; @@ -18,37 +17,11 @@ use tonic::Code; use tonic::Status; use tracing::debug; -use crate::NetworkError; -use crate::Result; use crate::stream::GrpcStreamDecoder; use d_engine_proto::common::LogId; use d_engine_proto::server::storage::SnapshotChunk; use d_engine_proto::server::storage::SnapshotMetadata; -/// Helper to create a valid snapshot stream -pub(crate) fn create_snapshot_stream( - chunks: usize, - chunk_size: usize, -) -> BoxStream<'static, Result> { - let chunks: Vec = (0..chunks) - .map(|seq| { - let data = vec![seq as u8; chunk_size]; - create_test_chunk( - seq as u32, - &data, - 1, // term - 1, // leader_id - chunks as u32, - ) - }) - .collect(); - - let stream = crate_test_snapshot_stream(chunks); - Box::pin( - stream.map(|item| item.map_err(|s| NetworkError::TonicStatusError(Box::new(s)).into())), - ) -} - pub fn crate_test_snapshot_stream(chunks: Vec) -> tonic::Streaming where T: prost::Message + Default + 'static, diff --git a/d-engine-core/src/utils/cluster.rs b/d-engine-core/src/utils/cluster.rs index 88ba4e2f..d809c6d5 100644 --- a/d-engine-core/src/utils/cluster.rs +++ b/d-engine-core/src/utils/cluster.rs @@ -1,20 +1,10 @@ -use dashmap::DashSet; -use metrics::gauge; -use std::sync::Arc; -use std::time::SystemTime; -use std::time::UNIX_EPOCH; use tracing::error; -use d_engine_proto::common::Entry; use d_engine_proto::common::NodeRole::Candidate; use d_engine_proto::common::NodeRole::Follower; use d_engine_proto::common::NodeRole::Leader; use d_engine_proto::common::NodeRole::Learner; -pub(crate) fn collect_ids(entries: &[Entry]) -> Vec { - entries.iter().map(|e| e.index).collect() -} - pub(crate) fn is_majority( num: usize, total: usize, @@ -26,33 +16,6 @@ pub fn majority_count(total_nodes: usize) -> usize { (total_nodes / 2) + 1 } -pub(crate) fn find_nearest_lower_number( - target_index: u64, - set_of_index: Arc>, -) -> Option { - set_of_index - .iter() - .filter(|index| **index <= target_index) // Filter only numbers <= learner_next_index - .max_by_key(|index| **index) // Find the maximum of the filtered numbers - .map(|index| *index) // Convert from reference to value -} - -/// record down cluster level error for debug and code optimization purpose. -pub(crate) fn record_down_cluster_error(event_id: u64) { - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_secs_f64(); - - let event_type = event_id.to_string(); - - gauge!( - "cluster_fatal_error_metric", - "event_type" => event_type - ) - .set(timestamp); -} - /// Format error logging pub fn error( func_name: &str, diff --git a/d-engine-core/src/utils/convert.rs b/d-engine-core/src/utils/convert.rs index 321b35ed..9c69d8e2 100644 --- a/d-engine-core/src/utils/convert.rs +++ b/d-engine-core/src/utils/convert.rs @@ -51,18 +51,6 @@ pub fn skv(name: String) -> Vec { name.encode_to_vec() } -/// return (high, low) -pub(crate) fn convert_u128_to_u64_with_high_and_low(n: u128) -> (u64, u64) { - ((n >> 64) as u64, n as u64) -} -/// return (high, low) -pub(crate) fn convert_high_and_low_fromu64_to_u128( - high: u64, - low: u64, -) -> u128 { - ((high as u128) << 64) | (low as u128) -} - /// abs_ceil(0.3) = 1 /// abs_ceil(0.5) = 1 /// abs_ceil(1.1) = 2 diff --git a/d-engine-core/src/utils/convert_test.rs b/d-engine-core/src/utils/convert_test.rs index 87a2c063..faf128ff 100644 --- a/d-engine-core/src/utils/convert_test.rs +++ b/d-engine-core/src/utils/convert_test.rs @@ -2,8 +2,6 @@ use std::fmt; use crate::convert::abs_ceil; use crate::convert::classify_error; -use crate::convert::convert_high_and_low_fromu64_to_u128; -use crate::convert::convert_u128_to_u64_with_high_and_low; use crate::convert::safe_kv; use crate::convert::safe_kv_bytes; use crate::convert::skv; @@ -61,42 +59,6 @@ fn test_skv() { assert!(!result.is_empty()); } -#[test] -fn test_convert_u128_to_u64_with_high_and_low() { - // Test basic conversion - let n: u128 = 0x1234_5678_9ABC_DEF0_0987_6543_21FE_DCBA; - let (high, low) = convert_u128_to_u64_with_high_and_low(n); - assert_eq!(high, 0x1234_5678_9ABC_DEF0); - assert_eq!(low, 0x0987_6543_21FE_DCBA); - - // Test zero - let (high, low) = convert_u128_to_u64_with_high_and_low(0); - assert_eq!(high, 0); - assert_eq!(low, 0); - - // Test max value - let (high, low) = convert_u128_to_u64_with_high_and_low(u128::MAX); - assert_eq!(high, u64::MAX); - assert_eq!(low, u64::MAX); -} - -#[test] -fn test_convert_high_and_low_fromu64_to_u128() { - // Test basic conversion - let high: u64 = 0x1234_5678_9ABC_DEF0; - let low: u64 = 0x0987_6543_21FE_DCBA; - let result = convert_high_and_low_fromu64_to_u128(high, low); - assert_eq!(result, 0x1234_5678_9ABC_DEF0_0987_6543_21FE_DCBA); - - // Test zero - let result = convert_high_and_low_fromu64_to_u128(0, 0); - assert_eq!(result, 0); - - // Test max values - let result = convert_high_and_low_fromu64_to_u128(u64::MAX, u64::MAX); - assert_eq!(result, u128::MAX); -} - #[test] fn test_abs_ceil() { // Test basic cases diff --git a/d-engine-core/src/utils/file_io.rs b/d-engine-core/src/utils/file_io.rs index 207f1ff1..d7050325 100644 --- a/d-engine-core/src/utils/file_io.rs +++ b/d-engine-core/src/utils/file_io.rs @@ -1,6 +1,5 @@ use std::fs::OpenOptions; use std::fs::create_dir_all; -use std::io::ErrorKind; use std::io::Read; use std::path::Path; use std::path::PathBuf; @@ -62,7 +61,8 @@ pub fn open_file_for_append(path: PathBuf) -> Result { Ok(log_file) } -pub(crate) async fn write_into_file( +#[allow(dead_code)] +pub async fn write_into_file( path: PathBuf, buf: Vec, ) { @@ -92,7 +92,8 @@ pub(crate) async fn write_into_file( /// /// # Error /// Returns a custom FileError error type, including various possible failure scenarios -pub(crate) async fn delete_file>(path: P) -> Result<()> { +#[allow(dead_code)] +pub async fn delete_file>(path: P) -> Result<()> { let path = path.as_ref(); let display_path = path.display().to_string(); @@ -225,80 +226,6 @@ pub fn convert_vec_checksum(checksum: Vec) -> Result<[u8; 32]> { } } -pub(crate) async fn move_directory( - temp_dir: &PathBuf, - final_dir: &PathBuf, -) -> Result<()> { - // Check if final_dir already exists - if fs::metadata(final_dir).await.is_ok() { - return Err(StorageError::PathError { - path: final_dir.to_path_buf(), - source: std::io::Error::new(ErrorKind::AlreadyExists, "Target directory exists"), - } - .into()); - } - - // Attempt to rename/move the directory - fs::rename(&temp_dir, &final_dir).await.map_err(|e| StorageError::PathError { - path: temp_dir.to_path_buf(), - source: e, - })?; - - Ok(()) -} - -pub(crate) async fn is_dir(path: &Path) -> Result { - let metadata = fs::metadata(path).await.map_err(StorageError::IoError)?; - - // Check if it's a directory - Ok(metadata.is_dir()) -} - -/// Computes a SHA-256 checksum for a directory by hashing the contents of its files. -/// -/// IMPORTANT: Files are processed in lexicographical order by filename. This is critical because: -/// 1. File system enumeration order (via `read_dir`) is implementation-defined and -/// non-deterministic -/// 2. SHA-256 is order-sensitive: `hash(fileA + fileB) ≠ hash(fileB + fileA)` -/// -/// The algorithm: -/// 1. Collects all top-level files in the directory (ignores subdirectories and symlinks) -/// 2. Sorts files by filename to ensure consistent processing order -/// 3. Reads each file's content in sorted order -/// 4. Updates hasher with each file's bytes sequentially -/// 5. Finalizes and returns the SHA-256 hash -/// -/// Notes: -/// - Non-files (directories/symlinks) are silently ignored -/// - Only top-level files are processed (no recursion into subdirectories) -/// - File read order is determined by filename sort, not creation time or modification time -/// - Empty directories will return the SHA-256 hash of empty data -pub(crate) async fn compute_checksum_from_folder_path(folder_path: &Path) -> Result<[u8; 32]> { - let mut hasher = Sha256::new(); - let mut entries = fs::read_dir(folder_path).await.map_err(StorageError::IoError)?; - - // Collect files while preserving DirEntry information - let mut files = Vec::new(); - while let Some(entry) = entries.next_entry().await.map_err(StorageError::IoError)? { - if entry.file_type().await.map_err(StorageError::IoError)?.is_file() { - files.push(entry); - } - } - - // Critical sorting: Filenames compared as OsString but sorted by simple byte order. - // This matches lexical order for valid UTF-8 names, and provides consistent ordering - // for non-UTF names across platforms. - files.sort_by_key(|entry| entry.file_name()); - - // Process files in deterministic sorted order - for entry in files { - let data = fs::read(entry.path()).await.map_err(StorageError::IoError)?; - hasher.update(data); - } - - Ok(hasher.finalize().into()) -} - /// Computes a SHA-256 checksum for a file by hashing its contents. /// /// The algorithm: @@ -311,7 +238,8 @@ pub(crate) async fn compute_checksum_from_folder_path(folder_path: &Path) -> Res /// - Processes the entire file content sequentially /// - Uses buffered reading to handle large files efficiently /// - Consistent across platforms and file systems -pub(crate) async fn compute_checksum_from_file_path(file_path: &Path) -> Result<[u8; 32]> { +#[allow(dead_code)] +pub async fn compute_checksum_from_file_path(file_path: &Path) -> Result<[u8; 32]> { let mut file = tokio::fs::File::open(file_path).await.map_err(StorageError::IoError)?; let mut hasher = Sha256::new(); @@ -366,3 +294,11 @@ pub(crate) fn validate_compressed_format(path: &Path) -> Result<()> { Ok(()) } + +#[allow(dead_code)] +pub(super) async fn is_dir(path: &Path) -> Result { + let metadata = fs::metadata(path).await.map_err(StorageError::IoError)?; + + // Check if it's a directory + Ok(metadata.is_dir()) +} diff --git a/d-engine-core/src/utils/file_io_test.rs b/d-engine-core/src/utils/file_io_test.rs index 586d15b5..1dd579db 100644 --- a/d-engine-core/src/utils/file_io_test.rs +++ b/d-engine-core/src/utils/file_io_test.rs @@ -14,11 +14,9 @@ use crate::FileError; use crate::StorageError; use crate::SystemError; use crate::file_io; -use crate::file_io::compute_checksum_from_folder_path; use crate::file_io::convert_vec_checksum; use crate::file_io::create_parent_dir_if_not_exist; use crate::file_io::delete_file; -use crate::file_io::move_directory; /// Passed: "/tmp/files/data.txt" /// Expected: "/tmp/files" created @@ -225,25 +223,6 @@ async fn test_delete_permission_denied() { fs::set_permissions(&dir_path, perms).await.unwrap(); } -#[tokio::test] -#[traced_test] -async fn test_move_directory() { - let temp_dir = tempfile::tempdir().unwrap(); - let temp_path = temp_dir.path().join("test_move_directory"); - - // Old directory path with trailing separator - let old_path = temp_path.join("old").join("a").join("b").join(""); // Mark as directory - create_parent_dir_if_not_exist(&old_path).unwrap(); - assert!(file_io::is_dir(&old_path).await.unwrap()); - - // New directory - let new_path = temp_path.join("new"); - move_directory(&old_path, &new_path).await.unwrap(); - - // Verify new directory exists and old is removed - assert!(file_io::is_dir(&new_path).await.unwrap()); - assert!(!old_path.exists()); -} #[test] fn test_convert_vec_checksum_converts_valid_checksum() { let input = vec![1; 32]; @@ -401,148 +380,6 @@ mod validate_compressed_format_tests { } } -#[cfg(test)] -mod compute_checksum_from_folder_path_tests { - - use super::*; - - /// Test computing checksum for an empty directory - #[tokio::test] - async fn test_compute_checksum_from_path_empty_dir() { - let temp_dir = tempdir().unwrap(); - let checksum = compute_checksum_from_folder_path(temp_dir.path()) - .await - .expect("Should compute checksum for empty dir"); - - // SHA-256 hash of empty data - let hasher = Sha256::new(); - let expected: [u8; 32] = hasher.finalize().into(); - - assert_eq!( - checksum, expected, - "Checksum for empty directory should be SHA-256 of empty data" - ); - } - - /// Test computing checksum for a directory with a single file - #[tokio::test] - async fn test_compute_checksum_from_path_single_file() { - let temp_dir = tempdir().unwrap(); - let file_path = temp_dir.path().join("test.txt"); - tokio::fs::write(&file_path, b"Hello, world!").await.unwrap(); - - let checksum = compute_checksum_from_folder_path(temp_dir.path()) - .await - .expect("Should compute checksum for directory with single file"); - - // Calculate expected SHA-256 - let mut hasher = Sha256::new(); - hasher.update(b"Hello, world!"); - let expected: [u8; 32] = hasher.finalize().into(); - - assert_eq!( - checksum, expected, - "Checksum should match SHA-256 of file content" - ); - } - - /// Test computing checksum for a directory with multiple files - #[tokio::test] - async fn test_compute_checksum_from_path_multiple_files() { - let temp_dir = tempdir().unwrap(); - - // Create files with different content - tokio::fs::write(temp_dir.path().join("file1.txt"), b"Content 1").await.unwrap(); - tokio::fs::write(temp_dir.path().join("file2.txt"), b"Content 2").await.unwrap(); - tokio::fs::write(temp_dir.path().join("file3.txt"), b"Content 3").await.unwrap(); - - let checksum = compute_checksum_from_folder_path(temp_dir.path()) - .await - .expect("Should compute checksum for directory with multiple files"); - - // Calculate expected SHA-256 (concatenated content of all files) - let mut hasher = Sha256::new(); - hasher.update(b"Content 1"); - hasher.update(b"Content 2"); - hasher.update(b"Content 3"); - let expected: [u8; 32] = hasher.finalize().into(); - - assert_eq!( - checksum, expected, - "Checksum should match SHA-256 of concatenated file contents" - ); - } - - /// Test that checksum ignores subdirectories and only processes files - #[tokio::test] - async fn test_compute_checksum_ignores_subdirectories() { - let temp_dir = tempdir().unwrap(); - - // Create a file and a subdirectory - tokio::fs::write(temp_dir.path().join("file.txt"), b"File content") - .await - .unwrap(); - let sub_dir = temp_dir.path().join("subdir"); - tokio::fs::create_dir(&sub_dir).await.unwrap(); - tokio::fs::write(sub_dir.join("ignored.txt"), b"Ignored content").await.unwrap(); - - let checksum = compute_checksum_from_folder_path(temp_dir.path()) - .await - .expect("Should compute checksum ignoring subdirectories"); - - // Calculate expected SHA-256 (only the top-level file) - let mut hasher = Sha256::new(); - hasher.update(b"File content"); - let expected: [u8; 32] = hasher.finalize().into(); - - assert_eq!( - checksum, expected, - "Checksum should only include top-level files" - ); - } - - /// Test error handling for non-existent directory - #[tokio::test] - async fn test_compute_checksum_nonexistent_dir() { - let temp_dir = tempdir().unwrap(); - let non_existent_path = temp_dir.path().join("does_not_exist"); - - let result = compute_checksum_from_folder_path(&non_existent_path).await; - - assert!( - result.is_err(), - "Should return error for non-existent directory" - ); - match result.unwrap_err() { - Error::System(SystemError::Storage(StorageError::IoError(_))) => {} // Expected - other => panic!("Expected IoError, got {other:?}"), - } - } - - /// Test checksum consistency across multiple runs - #[tokio::test] - async fn test_compute_checksum_consistency() { - let temp_dir = tempdir().unwrap(); - tokio::fs::write(temp_dir.path().join("data.bin"), b"Consistent data") - .await - .unwrap(); - - // Compute checksum twice - let checksum1 = compute_checksum_from_folder_path(temp_dir.path()) - .await - .expect("First computation should succeed"); - - let checksum2 = compute_checksum_from_folder_path(temp_dir.path()) - .await - .expect("Second computation should succeed"); - - assert_eq!( - checksum1, checksum2, - "Checksum should be consistent across multiple computations" - ); - } -} - #[cfg(test)] mod compute_checksum_from_file_path_tests { use super::*; diff --git a/d-engine-runtime/src/membership/raft_membership_test.rs b/d-engine-runtime/src/membership/raft_membership_test.rs index 9cb7992f..e1af2d23 100644 --- a/d-engine-runtime/src/membership/raft_membership_test.rs +++ b/d-engine-runtime/src/membership/raft_membership_test.rs @@ -8,14 +8,14 @@ use d_engine_core::ConsensusError; use d_engine_core::Error; use crate::RaftTypeConfig; -use crate::test_utils::MockNode; -use crate::test_utils::MockRpcService; use d_engine_core::Membership; use d_engine_core::MembershipError; use d_engine_core::MockStorageEngine; use d_engine_core::MockTypeConfig; use d_engine_core::RaftNodeConfig; use d_engine_core::ensure_safe_join; +use d_engine_core::test_utils::MockNode; +use d_engine_core::test_utils::MockRpcService; use d_engine_proto::common::AddNode; use d_engine_proto::common::BatchRemove; use d_engine_proto::common::MembershipChange; @@ -930,10 +930,10 @@ mod check_cluster_is_ready_test { use tokio::sync::oneshot; use super::*; - use crate::test_utils::MockNode; - use crate::test_utils::MockRpcService; use d_engine_core::MockStateMachine; use d_engine_core::MockStorageEngine; + use d_engine_core::test_utils::MockNode; + use d_engine_core::test_utils::MockRpcService; /// Case 1: Test all peers are healthy #[tokio::test] @@ -1173,7 +1173,7 @@ mod pre_warm_connections_tests { use super::*; use crate::net::address_str; - use crate::test_utils; + use d_engine_proto::common::NodeStatus; use d_engine_proto::server::cluster::NodeMeta; @@ -1228,7 +1228,9 @@ mod pre_warm_connections_tests { let is_ready = true; let mock_service = MockRpcService::default(); let (_port, addr) = - test_utils::MockNode::mock_listener(mock_service, rx, is_ready).await.unwrap(); + d_engine_core::test_utils::MockNode::mock_listener(mock_service, rx, is_ready) + .await + .unwrap(); (address_str(&addr.to_string()), tx) } diff --git a/d-engine-runtime/src/network/connection_cache_test.rs b/d-engine-runtime/src/network/connection_cache_test.rs index 96f04512..916af55c 100644 --- a/d-engine-runtime/src/network/connection_cache_test.rs +++ b/d-engine-runtime/src/network/connection_cache_test.rs @@ -6,11 +6,10 @@ use tracing_test::traced_test; use super::*; use crate::net::address_str; -use crate::test_utils::MockRpcService; -use crate::test_utils::{self}; use d_engine_core::ConnectionParams; use d_engine_core::ConnectionType; use d_engine_core::NetworkConfig; +use d_engine_core::test_utils::MockRpcService; // Helper to create test config fn test_config() -> NetworkConfig { @@ -27,7 +26,9 @@ async fn mock_address() -> (String, oneshot::Sender<()>) { let is_ready = true; let mock_service = MockRpcService::default(); let (_port, addr) = - test_utils::MockNode::mock_listener(mock_service, rx, is_ready).await.unwrap(); + d_engine_core::test_utils::MockNode::mock_listener(mock_service, rx, is_ready) + .await + .unwrap(); (address_str(&addr.to_string()), tx) } diff --git a/d-engine-runtime/src/network/grpc/grpc_transport_test.rs b/d-engine-runtime/src/network/grpc/grpc_transport_test.rs index 613e2bda..573ccf00 100644 --- a/d-engine-runtime/src/network/grpc/grpc_transport_test.rs +++ b/d-engine-runtime/src/network/grpc/grpc_transport_test.rs @@ -12,7 +12,6 @@ use tracing_test::traced_test; use super::*; use crate::grpc::grpc_transport::GrpcTransport; -use crate::test_utils::MockNode; use d_engine_core::ConnectionType; use d_engine_core::Error; use d_engine_core::MockMembership; @@ -24,6 +23,7 @@ use d_engine_core::SystemError; use d_engine_core::Transport; use d_engine_core::crate_test_snapshot_stream; use d_engine_core::create_test_chunk; +use d_engine_core::test_utils::MockNode; use d_engine_core::test_utils::node_config; use d_engine_proto::common::LogId; use d_engine_proto::common::NodeRole::Candidate; diff --git a/d-engine-runtime/src/network/health_checker_test.rs b/d-engine-runtime/src/network/health_checker_test.rs index fa280a9b..377a4b3b 100644 --- a/d-engine-runtime/src/network/health_checker_test.rs +++ b/d-engine-runtime/src/network/health_checker_test.rs @@ -3,11 +3,10 @@ use tokio::sync::oneshot; use tracing_test::traced_test; use super::*; -use crate::test_utils::MockNode; -use crate::test_utils::MockRpcService; -use crate::test_utils::{self}; use d_engine_core::ConnectionParams; use d_engine_core::NetworkConfig; +use d_engine_core::test_utils::MockNode; +use d_engine_core::test_utils::MockRpcService; /// Case 1: server is not ready #[tokio::test] @@ -16,7 +15,12 @@ async fn test_check_peer_is_ready_case1() { let (tx, rx) = oneshot::channel::<()>(); let is_ready = false; let mock_service = MockRpcService::default(); - let (_port, addr) = match test_utils::MockNode::mock_listener(mock_service, rx, is_ready).await + let (_port, addr) = match d_engine_core::test_utils::MockNode::mock_listener( + mock_service, + rx, + is_ready, + ) + .await { Ok(a) => a, Err(e) => { @@ -68,7 +72,12 @@ async fn test_check_peer_is_ready_case2() { let (tx, rx) = oneshot::channel::<()>(); let is_ready = true; let mock_service = MockRpcService::default(); - let (_port, addr) = match test_utils::MockNode::mock_listener(mock_service, rx, is_ready).await + let (_port, addr) = match d_engine_core::test_utils::MockNode::mock_listener( + mock_service, + rx, + is_ready, + ) + .await { Ok(a) => a, Err(e) => { diff --git a/d-engine-runtime/src/storage/buffered/buffered_raft_log.rs b/d-engine-runtime/src/storage/buffered/buffered_raft_log.rs index e176f9a3..78266a81 100644 --- a/d-engine-runtime/src/storage/buffered/buffered_raft_log.rs +++ b/d-engine-runtime/src/storage/buffered/buffered_raft_log.rs @@ -1187,6 +1187,16 @@ where pub fn len(&self) -> usize { self.entries.len() } + + /// Returns true if the buffer contains no entries. + /// + /// This complements the `len()` method to follow Rust API conventions. + /// Clippy requires: Any struct with a public `len` method should also + /// have a public `is_empty` method. + #[cfg(any(test, feature = "test-utils"))] + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } } impl Drop for BufferedRaftLog diff --git a/d-engine-runtime/src/test_utils/mock/mod.rs b/d-engine-runtime/src/test_utils/mock/mod.rs index 8853688d..eb2c5b20 100644 --- a/d-engine-runtime/src/test_utils/mock/mod.rs +++ b/d-engine-runtime/src/test_utils/mock/mod.rs @@ -1,8 +1,8 @@ mod mock_node_builder; -mod mock_rpc_services; +// mod mock_rpc_services; pub use mock_node_builder::*; -pub use mock_rpc_services::*; +// pub use mock_rpc_services::*; use crate::Node; use d_engine_core::node_config; From c3b9cdf1c569b5b8b3f513e5740a69d3b6375058 Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Thu, 30 Oct 2025 21:45:36 +0800 Subject: [PATCH 16/26] feat #167: enable profile optimization --- Cargo.toml | 54 +++++++++---------- Makefile | 16 +++--- d-engine-core/src/raft.rs | 14 ++--- .../buffered/buffered_raft_log_test.rs | 5 +- 4 files changed, 45 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6d34da5d..16b4a308 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,30 +110,30 @@ tempfile = "3.19.1" # tonic-build = "0.12" # vergen = { version = "8.1", features = ["git", "gitcl"] } -# [profile.dev] -# opt-level = 1 # Main code is low-optimized to speed up compilation -# incremental = true # Enable incremental compilation (enabled by default, requires confirmation) -# codegen-units = 16 # Appropriately reduce parallel units to reduce memory usage -# lto = false - -# [profile.dev.package."*"] -# opt-level = 3 # All dependency optimization levels - -# [profile.release] -# incremental = true -# debug = false -# # opt-level = 3 -# opt-level = 'z' # Optimized for minimum size -# overflow-checks = true -# lto = true # Eliminate redundant code and reduce size -# codegen-units = 1 # Reduce parallel code generation units to improve optimization effect - - -# [profile.test] -# incremental = true -# codegen-units = 16 # Take advantage of M2 multi-core -# split-debuginfo = "unpacked" # macOS-specific optimization -# debug = 1 # Reduce debug information - -# [profile.test.build-override] -# opt-level = 3 # Full optimization of dependencies +[profile.dev] +opt-level = 1 # Main code is low-optimized to speed up compilation +incremental = true # Enable incremental compilation (enabled by default, requires confirmation) +codegen-units = 16 # Appropriately reduce parallel units to reduce memory usage +lto = false + +[profile.dev.package."*"] +opt-level = 3 # All dependency optimization levels + +[profile.release] +incremental = true +debug = false +# opt-level = 3 +opt-level = 'z' # Optimized for minimum size +overflow-checks = true +lto = true # Eliminate redundant code and reduce size +codegen-units = 1 # Reduce parallel code generation units to improve optimization effect + + +[profile.test] +incremental = true +codegen-units = 16 # Take advantage of M2 multi-core +split-debuginfo = "unpacked" # macOS-specific optimization +debug = 1 # Reduce debug information + +[profile.test.build-override] +opt-level = 3 # Full optimization of dependencies diff --git a/Makefile b/Makefile index 4c3b3164..fe6233eb 100644 --- a/Makefile +++ b/Makefile @@ -198,28 +198,26 @@ build-release: check check-workspace # TESTING # ============================================================================ -## test Run unit and integration tests (with logging) +## test Run all tests (lib + bins + examples + integration) test: install-tools check-workspace - @echo "$(BLUE)Running unit and integration tests...$(NC)" + @echo "$(BLUE)Running tests on all targets...$(NC)" @RUST_LOG=$(RUST_LOG_LEVEL) RUST_BACKTRACE=$(RUST_BACKTRACE) \ - $(CARGO) test --workspace --lib --all-targets --no-fail-fast -- --test-threads=1 --nocapture - @echo "$(BLUE)Running integration tests...$(NC)" - @RUST_LOG=$(RUST_LOG_LEVEL) RUST_BACKTRACE=$(RUST_BACKTRACE) \ - $(CARGO) test --workspace --test '*' --no-fail-fast -- --nocapture + $(CARGO) test --workspace --all-targets --no-fail-fast -- --test-threads=1 --nocapture @echo "$(GREEN)✓ All tests passed$(NC)" -## test-unit Run unit tests only (--lib flag) +## test-unit Run unit tests only (library code only) test-unit: install-tools check-workspace - @echo "$(BLUE)Running unit tests...$(NC)" + @echo "$(BLUE)Running unit tests (lib only)...$(NC)" @RUST_LOG=$(RUST_LOG_LEVEL) RUST_BACKTRACE=$(RUST_BACKTRACE) \ $(CARGO) test --workspace --lib --no-fail-fast -- --nocapture @echo "$(GREEN)✓ Unit tests passed$(NC)" + ## test-integration Run integration tests only (--test flag) test-integration: install-tools check-workspace @echo "$(BLUE)Running integration tests...$(NC)" @RUST_LOG=$(RUST_LOG_LEVEL) RUST_BACKTRACE=$(RUST_BACKTRACE) \ - $(CARGO) test --workspace --test '*' --no-fail-fast -- --nocapture + $(CARGO) test --workspace --tests --no-fail-fast -- --nocapture @echo "$(GREEN)✓ Integration tests passed$(NC)" ## test-doc Run documentation tests only diff --git a/d-engine-core/src/raft.rs b/d-engine-core/src/raft.rs index 14c67c32..c2bae693 100644 --- a/d-engine-core/src/raft.rs +++ b/d-engine-core/src/raft.rs @@ -16,7 +16,7 @@ use super::RaftEvent; use super::RaftRole; use super::RaftStorageHandles; use super::RoleEvent; -#[cfg(test)] +#[cfg(any(test, feature = "test-utils"))] use super::raft_event_to_test_event; use crate::Membership; use crate::NetworkError; @@ -209,14 +209,14 @@ where Some(raft_event) = self.event_rx.recv() => { trace!(%self.node_id, ?raft_event, "receive raft event"); - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] let event = raft_event_to_test_event(&raft_event); if let Err(e) = self.role.handle_raft_event(raft_event, &self.ctx, self.role_tx.clone()).await { error!(%self.node_id, ?e, "handle_raft_event error"); } - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] self.notify_raft_event(event); } @@ -237,7 +237,7 @@ where debug!("BecomeFollower"); self.role = self.role.become_follower()?; - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] self.notify_role_transition(); //TODO: update membership @@ -246,7 +246,7 @@ where debug!("BecomeCandidate"); self.role = self.role.become_candidate()?; - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] self.notify_role_transition(); } RoleEvent::BecomeLeader => { @@ -282,14 +282,14 @@ where })?; } - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] self.notify_role_transition(); } RoleEvent::BecomeLearner => { debug!("BecomeLearner"); self.role = self.role.become_learner()?; - #[cfg(test)] + #[cfg(any(test, feature = "test-utils"))] self.notify_role_transition(); } RoleEvent::NotifyNewCommitIndex(new_commit_data) => { diff --git a/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs b/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs index 5dbbcbbd..40138477 100644 --- a/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs +++ b/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs @@ -3424,7 +3424,10 @@ mod remove_range_tests { let duration = start.elapsed(); println!("Removed 50,000 entries in {duration:?}"); - assert!(duration < std::time::Duration::from_millis(100)); + assert!( + duration < std::time::Duration::from_millis(100), + "Operations took too long: {duration:?}" + ); // Verify state assert_eq!(context.raft_log.len(), 50_000); From 483f04a630c079deaf0f62698ed8d30b2a3d52fe Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Thu, 30 Oct 2025 22:03:25 +0800 Subject: [PATCH 17/26] feat #167: new makefile --- Cargo.lock | 5 ++++ Makefile | 45 +++++++++++++++++++++++++++--- d-engine-docs/Cargo.toml | 4 +++ d-engine-docs/src/docs/overview.md | 6 ++-- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f3ba5c0..512d0f60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -468,6 +468,11 @@ dependencies = [ [[package]] name = "d-engine-docs" version = "0.2.0" +dependencies = [ + "d-engine-runtime", + "tokio", + "tracing", +] [[package]] name = "d-engine-proto" diff --git a/Makefile b/Makefile index fe6233eb..7721736a 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,8 @@ .PHONY: help all check fmt fmt-check clippy clippy-fix test test-unit \ test-integration test-doc bench clean doc build build-release \ - pre-release install check-env audit deny troubleshoot + pre-release install check-env audit deny troubleshoot \ + test-crate test-examples test-detailed # ============================================================================ # CONFIGURATION SECTION @@ -65,6 +66,7 @@ help: @echo "" @echo " $(YELLOW)Testing:$(NC)" @echo " make test # Run unit + integration tests" + @echo " make test-detailed # Run tests with detailed failure output" @echo " make test-all # Run all tests including benchmarks" @echo " make test-unit # Run unit tests only" @echo "" @@ -205,6 +207,24 @@ test: install-tools check-workspace $(CARGO) test --workspace --all-targets --no-fail-fast -- --test-threads=1 --nocapture @echo "$(GREEN)✓ All tests passed$(NC)" +## test-detailed Run tests with detailed failure output for each crate +test-detailed: install-tools check-workspace + @echo "$(BLUE)Running tests with detailed output per crate...$(NC)" + @for member in $(WORKSPACE_MEMBERS); do \ + echo "$(CYAN)Testing crate: $$member$(NC)"; \ + RUST_LOG=$(RUST_LOG_LEVEL) RUST_BACKTRACE=$(RUST_BACKTRACE) \ + $(CARGO) test -p $$member --all-targets --no-fail-fast -- --test-threads=1 --nocapture || \ + { echo "$(RED)✗ Tests failed in crate: $$member$(NC)"; exit 1; }; \ + echo "$(GREEN)✓ Tests passed for crate: $$member$(NC)"; \ + echo ""; \ + done + @echo "$(BLUE)Running examples tests...$(NC)" + @RUST_LOG=$(RUST_LOG_LEVEL) RUST_BACKTRACE=$(RUST_BACKTRACE) \ + $(CARGO) test --workspace --examples --no-fail-fast -- --test-threads=1 --nocapture || \ + { echo "$(RED)✗ Examples tests failed$(NC)"; exit 1; } + @echo "$(GREEN)✓ All examples tests passed$(NC)" + @echo "$(GREEN)✓ All tests passed with detailed output$(NC)" + ## test-unit Run unit tests only (library code only) test-unit: install-tools check-workspace @echo "$(BLUE)Running unit tests (lib only)...$(NC)" @@ -212,7 +232,6 @@ test-unit: install-tools check-workspace $(CARGO) test --workspace --lib --no-fail-fast -- --nocapture @echo "$(GREEN)✓ Unit tests passed$(NC)" - ## test-integration Run integration tests only (--test flag) test-integration: install-tools check-workspace @echo "$(BLUE)Running integration tests...$(NC)" @@ -226,15 +245,33 @@ test-doc: install-tools check-workspace @$(CARGO) test --doc --workspace @echo "$(GREEN)✓ Documentation tests passed$(NC)" +## test-examples Run tests for examples only +test-examples: install-tools check-workspace + @echo "$(BLUE)Running examples tests...$(NC)" + @RUST_LOG=$(RUST_LOG_LEVEL) RUST_BACKTRACE=$(RUST_BACKTRACE) \ + $(CARGO) test --workspace --examples --no-fail-fast -- --test-threads=1 --nocapture + @echo "$(GREEN)✓ Examples tests passed$(NC)" + +## test-crate Run tests for a specific crate (usage: make test-crate CRATE=d-engine-runtime) +test-crate: install-tools check-workspace +ifndef CRATE + @echo "$(RED)Error: CRATE variable not set. Usage: make test-crate CRATE=crate-name$(NC)" + @exit 1 +endif + @echo "$(BLUE)Running tests for crate: $(CRATE)$(NC)" + @RUST_LOG=$(RUST_LOG_LEVEL) RUST_BACKTRACE=$(RUST_BACKTRACE) \ + $(CARGO) test -p $(CRATE) --all-targets --no-fail-fast -- --test-threads=1 --nocapture --show-output + @echo "$(GREEN)✓ All tests passed for crate: $(CRATE)$(NC)" + ## test-all Run all tests: unit + integration + doc + benchmarks -test-all: test test-doc bench +test-all: test-detailed test-doc bench @echo "$(GREEN)✓ All test suites passed$(NC)" ## test-verbose Run tests with verbose output and single-threaded execution test-verbose: install-tools check-workspace @echo "$(BLUE)Running tests (verbose, single-threaded)...$(NC)" @RUST_LOG=$(RUST_LOG_LEVEL) RUST_BACKTRACE=$(RUST_BACKTRACE) \ - $(CARGO) test --workspace -- --nocapture --test-threads=1 + $(CARGO) test --workspace --all-targets --no-fail-fast -- --nocapture --test-threads=1 --show-output @echo "$(GREEN)✓ Verbose test run completed$(NC)" # ============================================================================ diff --git a/d-engine-docs/Cargo.toml b/d-engine-docs/Cargo.toml index 58fca17e..07c45023 100644 --- a/d-engine-docs/Cargo.toml +++ b/d-engine-docs/Cargo.toml @@ -4,3 +4,7 @@ version = "0.2.0" edition = "2024" [dependencies] +d-engine-runtime = { workspace = true } + +tokio = { workspace = true } +tracing = { workspace = true } diff --git a/d-engine-docs/src/docs/overview.md b/d-engine-docs/src/docs/overview.md index 1e771cf7..05eecc9c 100644 --- a/d-engine-docs/src/docs/overview.md +++ b/d-engine-docs/src/docs/overview.md @@ -25,9 +25,9 @@ ## Quick Start ```no_run -use d_engine::NodeBuilder; -use d_engine::FileStorageEngine; -use d_engine::FileStateMachine; +use d_engine_runtime::NodeBuilder; +use d_engine_runtime::FileStorageEngine; +use d_engine_runtime::FileStateMachine; use tokio::sync::watch; use tracing::error; use tracing::info; From 658191b67eca3453cda886e62e8b5bd8d5ee7004 Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Thu, 30 Oct 2025 22:06:11 +0800 Subject: [PATCH 18/26] feat #167: fmt fix --- .github/workflows/ci.yml | 1 + d-engine-runtime/src/membership/raft_membership_test.rs | 2 +- .../tests/components/raft_role/learner_state_test.rs | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bdeda20e..b3f865fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,7 @@ jobs: ~/.cargo/registry ~/.cargo/git target + ~/.cargo/bin key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo- diff --git a/d-engine-runtime/src/membership/raft_membership_test.rs b/d-engine-runtime/src/membership/raft_membership_test.rs index e1af2d23..d201245d 100644 --- a/d-engine-runtime/src/membership/raft_membership_test.rs +++ b/d-engine-runtime/src/membership/raft_membership_test.rs @@ -1173,7 +1173,7 @@ mod pre_warm_connections_tests { use super::*; use crate::net::address_str; - + use d_engine_proto::common::NodeStatus; use d_engine_proto::server::cluster::NodeMeta; diff --git a/d-engine-runtime/tests/components/raft_role/learner_state_test.rs b/d-engine-runtime/tests/components/raft_role/learner_state_test.rs index 4a50947f..e5e9a30a 100644 --- a/d-engine-runtime/tests/components/raft_role/learner_state_test.rs +++ b/d-engine-runtime/tests/components/raft_role/learner_state_test.rs @@ -626,7 +626,6 @@ fn mock_context( ) -> RaftContext { let temp_dir = tempfile::tempdir().unwrap(); let case_path = temp_dir.path().join(case_name); - mock_raft_context(case_path.to_str().unwrap(), shutdown_signal, None) } From fcb4e3ab51992df63e64c80dc34b7d39a6adb24e Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Thu, 30 Oct 2025 23:57:25 +0800 Subject: [PATCH 19/26] feat #167: add unit tests for ext --- Cargo.toml | 8 + d-engine-proto/src/exts/client_ext_test.rs | 297 ++++++++++++++++++ d-engine-proto/src/exts/cluster_ext_test.rs | 139 ++++++++ d-engine-proto/src/exts/common_ext_test.rs | 164 ++++++++++ d-engine-proto/src/exts/mod.rs | 11 + .../src/exts/replication_ext_test.rs | 145 +++++++++ d-engine-proto/src/exts/storage_ext_test.rs | 96 ++++++ 7 files changed, 860 insertions(+) create mode 100644 d-engine-proto/src/exts/client_ext_test.rs create mode 100644 d-engine-proto/src/exts/cluster_ext_test.rs create mode 100644 d-engine-proto/src/exts/common_ext_test.rs create mode 100644 d-engine-proto/src/exts/replication_ext_test.rs create mode 100644 d-engine-proto/src/exts/storage_ext_test.rs diff --git a/Cargo.toml b/Cargo.toml index 16b4a308..fee3a640 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,4 @@ + [workspace] members = [ "d-engine-proto", @@ -7,6 +8,13 @@ members = [ "d-engine-docs", ] +exclude = [ + "examples/client_usage", + "examples/rocksdb-cluster", + "examples/sled-cluster", + "examples/three-nodes-cluster", +] + resolver = "2" [workspace.dependencies] diff --git a/d-engine-proto/src/exts/client_ext_test.rs b/d-engine-proto/src/exts/client_ext_test.rs new file mode 100644 index 00000000..e3dc8afc --- /dev/null +++ b/d-engine-proto/src/exts/client_ext_test.rs @@ -0,0 +1,297 @@ +use crate::client::{ + client_response::SuccessResult, ClientReadRequest, ClientResponse, ClientResult, + ReadConsistencyPolicy, WriteCommand, +}; +use crate::error::ErrorCode; +use bytes::Bytes; + +#[test] +fn test_client_read_request_has_consistency_policy_false() { + let request = ClientReadRequest { + client_id: 1, + keys: vec![], + consistency_policy: None, + }; + + assert!(!request.has_consistency_policy()); +} + +#[test] +fn test_client_read_request_has_consistency_policy_true() { + let request = ClientReadRequest { + client_id: 1, + keys: vec![], + consistency_policy: Some(ReadConsistencyPolicy::LeaseRead as i32), + }; + + assert!(request.has_consistency_policy()); +} + +#[test] +fn test_client_read_request_get_consistency_policy_none() { + let request = ClientReadRequest { + client_id: 1, + keys: vec![], + consistency_policy: None, + }; + + assert_eq!(request.get_consistency_policy(), None); +} + +#[test] +fn test_client_read_request_get_consistency_policy_lease_read() { + let request = ClientReadRequest { + client_id: 1, + keys: vec![], + consistency_policy: Some(ReadConsistencyPolicy::LeaseRead as i32), + }; + + assert_eq!( + request.get_consistency_policy(), + Some(ReadConsistencyPolicy::LeaseRead) + ); +} + +#[test] +fn test_client_read_request_get_consistency_policy_linearizable_read() { + let request = ClientReadRequest { + client_id: 1, + keys: vec![], + consistency_policy: Some(ReadConsistencyPolicy::LinearizableRead as i32), + }; + + assert_eq!( + request.get_consistency_policy(), + Some(ReadConsistencyPolicy::LinearizableRead) + ); +} + +#[test] +fn test_client_read_request_get_consistency_policy_invalid() { + let request = ClientReadRequest { + client_id: 1, + keys: vec![], + consistency_policy: Some(999), // Invalid value + }; + + assert_eq!(request.get_consistency_policy(), None); +} + +#[test] +fn test_write_command_insert_creation() { + let key = "test_key"; + let value = "test_value"; + let cmd = WriteCommand::insert(key, value); + + match cmd.operation { + Some(crate::client::write_command::Operation::Insert(insert_cmd)) => { + assert_eq!(insert_cmd.key, Bytes::from("test_key")); + assert_eq!(insert_cmd.value, Bytes::from("test_value")); + } + _ => panic!("Expected Insert operation"), + } +} + +#[test] +fn test_write_command_insert_with_bytes() { + let key = Bytes::from("key"); + let value = Bytes::from("value"); + let cmd = WriteCommand::insert(key.clone(), value.clone()); + + match cmd.operation { + Some(crate::client::write_command::Operation::Insert(insert_cmd)) => { + assert_eq!(insert_cmd.key, key); + assert_eq!(insert_cmd.value, value); + } + _ => panic!("Expected Insert operation"), + } +} + +#[test] +fn test_write_command_delete_creation() { + let key = "delete_key"; + let cmd = WriteCommand::delete(key); + + match cmd.operation { + Some(crate::client::write_command::Operation::Delete(delete_cmd)) => { + assert_eq!(delete_cmd.key, Bytes::from("delete_key")); + } + _ => panic!("Expected Delete operation"), + } +} + +#[test] +fn test_write_command_delete_with_bytes() { + let key = Bytes::from("my_key"); + let cmd = WriteCommand::delete(key.clone()); + + match cmd.operation { + Some(crate::client::write_command::Operation::Delete(delete_cmd)) => { + assert_eq!(delete_cmd.key, key); + } + _ => panic!("Expected Delete operation"), + } +} + +#[test] +fn test_client_response_write_success() { + let response = ClientResponse::write_success(); + + assert_eq!(response.error, ErrorCode::Success as i32); + assert!(response.success_result.is_some()); + assert_eq!(response.metadata, None); + + match response.success_result { + Some(SuccessResult::WriteAck(true)) => { + // Success + } + _ => panic!("Expected WriteAck success"), + } +} + +#[test] +fn test_client_response_is_write_success() { + let response = ClientResponse::write_success(); + assert!(response.is_write_success()); +} + +#[test] +fn test_client_response_is_write_success_false() { + let response = ClientResponse::client_error(ErrorCode::ProposeFailed); + assert!(!response.is_write_success()); +} + +#[test] +fn test_client_response_read_results() { + let results = vec![ + ClientResult { + key: Bytes::from("key1"), + value: Bytes::from("value1"), + }, + ClientResult { + key: Bytes::from("key2"), + value: Bytes::from("value2"), + }, + ]; + + let response = ClientResponse::read_results(results.clone()); + + assert_eq!(response.error, ErrorCode::Success as i32); + match response.success_result { + Some(SuccessResult::ReadData(read_results)) => { + assert_eq!(read_results.results.len(), 2); + assert_eq!(read_results.results[0].key, Bytes::from("key1")); + assert_eq!(read_results.results[0].value, Bytes::from("value1")); + } + _ => panic!("Expected ReadData success"), + } +} + +#[test] +fn test_client_response_client_error() { + let response = ClientResponse::client_error(ErrorCode::ConnectionTimeout); + + assert_eq!(response.error, ErrorCode::ConnectionTimeout as i32); + assert_eq!(response.success_result, None); + assert_eq!(response.metadata, None); +} + +#[test] +fn test_client_response_is_term_outdated() { + let response = ClientResponse::client_error(ErrorCode::TermOutdated); + assert!(response.is_term_outdated()); +} + +#[test] +fn test_client_response_is_term_outdated_false() { + let response = ClientResponse::client_error(ErrorCode::ConnectionTimeout); + assert!(!response.is_term_outdated()); +} + +#[test] +fn test_client_response_is_quorum_timeout_or_failure() { + assert!( + ClientResponse::client_error(ErrorCode::ConnectionTimeout).is_quorum_timeout_or_failure() + ); + assert!(ClientResponse::client_error(ErrorCode::ProposeFailed).is_quorum_timeout_or_failure()); + assert!( + ClientResponse::client_error(ErrorCode::ClusterUnavailable).is_quorum_timeout_or_failure() + ); +} + +#[test] +fn test_client_response_is_propose_failure() { + let response = ClientResponse::client_error(ErrorCode::ProposeFailed); + assert!(response.is_propose_failure()); +} + +#[test] +fn test_client_response_is_retry_required() { + let response = ClientResponse::client_error(ErrorCode::RetryRequired); + assert!(response.is_retry_required()); +} + +#[test] +fn test_error_code_is_term_outdated() { + assert!(ErrorCode::TermOutdated.is_term_outdated()); + assert!(!ErrorCode::Success.is_term_outdated()); +} + +#[test] +fn test_error_code_is_propose_failure() { + assert!(ErrorCode::ProposeFailed.is_propose_failure()); + assert!(!ErrorCode::Success.is_propose_failure()); +} + +#[test] +fn test_error_code_is_retry_required() { + assert!(ErrorCode::RetryRequired.is_retry_required()); + assert!(!ErrorCode::Success.is_retry_required()); +} + +#[test] +fn test_write_command_insert_empty_key_value() { + let cmd = WriteCommand::insert("", ""); + + match cmd.operation { + Some(crate::client::write_command::Operation::Insert(insert_cmd)) => { + assert_eq!(insert_cmd.key, Bytes::from("")); + assert_eq!(insert_cmd.value, Bytes::from("")); + } + _ => panic!("Expected Insert operation"), + } +} + +#[test] +fn test_write_command_insert_large_data() { + // Create byte vectors directly instead of going through String + let large_key = vec![b'a'; 1000]; + let large_value = vec![b'b'; 10000]; + + // Convert to Bytes before passing to insert + let cmd = WriteCommand::insert(Bytes::from(large_key), Bytes::from(large_value)); + + match cmd.operation { + Some(crate::client::write_command::Operation::Insert(insert_cmd)) => { + assert_eq!(insert_cmd.key.len(), 1000); + assert_eq!(insert_cmd.value.len(), 10000); + // Verify the content is correct + assert!(insert_cmd.key.iter().all(|&b| b == b'a')); + assert!(insert_cmd.value.iter().all(|&b| b == b'b')); + } + _ => panic!("Expected Insert operation"), + } +} + +#[test] +fn test_client_response_read_results_empty() { + let response = ClientResponse::read_results(vec![]); + + assert_eq!(response.error, ErrorCode::Success as i32); + match response.success_result { + Some(SuccessResult::ReadData(read_results)) => { + assert_eq!(read_results.results.len(), 0); + } + _ => panic!("Expected ReadData success"), + } +} diff --git a/d-engine-proto/src/exts/cluster_ext_test.rs b/d-engine-proto/src/exts/cluster_ext_test.rs new file mode 100644 index 00000000..12477094 --- /dev/null +++ b/d-engine-proto/src/exts/cluster_ext_test.rs @@ -0,0 +1,139 @@ +use crate::common::NodeStatus; +use crate::server::cluster::{cluster_conf_update_response::ErrorCode, ClusterConfUpdateResponse}; + +#[test] +fn test_cluster_conf_update_response_success() { + let response = ClusterConfUpdateResponse::success(1, 5, 10); + + assert_eq!(response.id, 1); + assert_eq!(response.term, 5); + assert_eq!(response.version, 10); + assert!(response.success); + assert_eq!(response.error_code, ErrorCode::None as i32); +} + +#[test] +fn test_cluster_conf_update_response_higher_term() { + let response = ClusterConfUpdateResponse::higher_term(2, 6, 11); + + assert_eq!(response.id, 2); + assert_eq!(response.term, 6); + assert_eq!(response.version, 11); + assert!(!response.success); + assert_eq!(response.error_code, ErrorCode::TermOutdated as i32); +} + +#[test] +fn test_cluster_conf_update_response_not_leader() { + let response = ClusterConfUpdateResponse::not_leader(3, 7, 12); + + assert_eq!(response.id, 3); + assert_eq!(response.term, 7); + assert_eq!(response.version, 12); + assert!(!response.success); + assert_eq!(response.error_code, ErrorCode::NotLeader as i32); +} + +#[test] +fn test_cluster_conf_update_response_version_conflict() { + let response = ClusterConfUpdateResponse::version_conflict(4, 8, 13); + + assert_eq!(response.id, 4); + assert_eq!(response.term, 8); + assert_eq!(response.version, 13); + assert!(!response.success); + assert_eq!(response.error_code, ErrorCode::VersionConflict as i32); +} + +#[test] +fn test_cluster_conf_update_response_invalid_change() { + let response = ClusterConfUpdateResponse::invalid_change(5, 9, 14); + + assert_eq!(response.id, 5); + assert_eq!(response.term, 9); + assert_eq!(response.version, 14); + assert!(!response.success); + assert_eq!(response.error_code, ErrorCode::InvalidChange as i32); +} + +#[test] +fn test_cluster_conf_update_response_internal_error() { + let response = ClusterConfUpdateResponse::internal_error(6, 10, 15); + + assert_eq!(response.id, 6); + assert_eq!(response.term, 10); + assert_eq!(response.version, 15); + assert!(!response.success); + assert_eq!(response.error_code, ErrorCode::InternalError as i32); +} + +#[test] +fn test_cluster_conf_update_response_is_higher_term() { + let response = ClusterConfUpdateResponse::higher_term(1, 5, 10); + assert!(response.is_higher_term()); +} + +#[test] +fn test_cluster_conf_update_response_is_higher_term_false() { + let response = ClusterConfUpdateResponse::success(1, 5, 10); + assert!(!response.is_higher_term()); +} + +#[test] +fn test_cluster_conf_update_response_all_error_types() { + let success = ClusterConfUpdateResponse::success(1, 1, 1); + let higher_term = ClusterConfUpdateResponse::higher_term(2, 2, 2); + let not_leader = ClusterConfUpdateResponse::not_leader(3, 3, 3); + let version_conflict = ClusterConfUpdateResponse::version_conflict(4, 4, 4); + let internal_error = ClusterConfUpdateResponse::internal_error(5, 5, 5); + + assert!(success.success); + assert!(!higher_term.success); + assert!(!not_leader.success); + assert!(!version_conflict.success); + assert!(!internal_error.success); +} + +#[test] +fn test_node_status_is_promotable_syncing() { + // Syncing is the only promotable status according to is_promotable implementation + assert!(NodeStatus::Syncing.is_promotable()); +} + +#[test] +fn test_node_status_is_promotable_other_variants() { + // Other variants should not be promotable + assert!(!NodeStatus::Joining.is_promotable()); + assert!(!NodeStatus::Active.is_promotable()); + assert!(!NodeStatus::StandBy.is_promotable()); + assert!(!NodeStatus::Draining.is_promotable()); +} + +#[test] +fn test_node_status_is_i32_promotable() { + assert!(NodeStatus::is_i32_promotable(NodeStatus::Syncing as i32)); + assert!(!NodeStatus::is_i32_promotable(NodeStatus::Joining as i32)); + assert!(!NodeStatus::is_i32_promotable(NodeStatus::Active as i32)); + assert!(!NodeStatus::is_i32_promotable(NodeStatus::StandBy as i32)); + assert!(!NodeStatus::is_i32_promotable(NodeStatus::Draining as i32)); +} + +#[test] +fn test_cluster_conf_update_response_large_values() { + let response = ClusterConfUpdateResponse::success(u32::MAX, u64::MAX, u64::MAX); + + assert_eq!(response.id, u32::MAX); + assert_eq!(response.term, u64::MAX); + assert_eq!(response.version, u64::MAX); + assert!(response.success); +} + +#[test] +fn test_cluster_conf_update_response_zero_values() { + let response = ClusterConfUpdateResponse::success(0, 0, 0); + + assert_eq!(response.id, 0); + assert_eq!(response.term, 0); + assert_eq!(response.version, 0); + assert!(response.success); +} diff --git a/d-engine-proto/src/exts/common_ext_test.rs b/d-engine-proto/src/exts/common_ext_test.rs new file mode 100644 index 00000000..33ac13a6 --- /dev/null +++ b/d-engine-proto/src/exts/common_ext_test.rs @@ -0,0 +1,164 @@ +use crate::common::{ + entry_payload::Payload, membership_change::Change, AddNode, EntryPayload, RemoveNode, +}; +use bytes::Bytes; + +#[test] +fn test_entry_payload_command_creation() { + let command_data = Bytes::from("test_command"); + let payload = EntryPayload::command(command_data.clone()); + + assert!(payload.is_command()); + assert!(!payload.is_noop()); + assert!(!payload.is_config()); + + match payload.payload { + Some(Payload::Command(cmd)) => { + assert_eq!(cmd, command_data); + } + _ => panic!("Expected Command payload"), + } +} + +#[test] +fn test_entry_payload_noop_creation() { + let payload = EntryPayload::noop(); + + assert!(payload.is_noop()); + assert!(!payload.is_command()); + assert!(!payload.is_config()); + + match payload.payload { + Some(Payload::Noop(_)) => { + // Success + } + _ => panic!("Expected Noop payload"), + } +} + +#[test] +fn test_entry_payload_config_with_add_node() { + let add_node = AddNode { + node_id: 1, + address: "127.0.0.1:5000".to_string(), + }; + let change = Change::AddNode(add_node.clone()); + let payload = EntryPayload::config(change); + + assert!(payload.is_config()); + assert!(!payload.is_command()); + assert!(!payload.is_noop()); + + match payload.payload { + Some(Payload::Config(config)) => match config.change { + Some(Change::AddNode(node)) => { + assert_eq!(node.node_id, 1); + assert_eq!(node.address, "127.0.0.1:5000"); + } + _ => panic!("Expected AddNode change"), + }, + _ => panic!("Expected Config payload"), + } +} + +#[test] +fn test_entry_payload_config_with_remove_node() { + let remove_node = RemoveNode { node_id: 2 }; + let change = Change::RemoveNode(remove_node); + let payload = EntryPayload::config(change); + + assert!(payload.is_config()); + assert!(!payload.is_command()); + assert!(!payload.is_noop()); + + match payload.payload { + Some(Payload::Config(config)) => match config.change { + Some(Change::RemoveNode(node)) => { + assert_eq!(node.node_id, 2); + } + _ => panic!("Expected RemoveNode change"), + }, + _ => panic!("Expected Config payload"), + } +} + +#[test] +fn test_entry_payload_empty_payload() { + let payload = EntryPayload { payload: None }; + + assert!(!payload.is_command()); + assert!(!payload.is_noop()); + assert!(!payload.is_config()); +} + +#[test] +fn test_entry_payload_command_with_empty_bytes() { + let command_data = Bytes::new(); + let payload = EntryPayload::command(command_data); + + assert!(payload.is_command()); + assert!(!payload.is_noop()); + assert!(!payload.is_config()); +} + +#[test] +fn test_entry_payload_command_with_large_data() { + let large_data = Bytes::from(vec![0u8; 10_000]); + let payload = EntryPayload::command(large_data.clone()); + + assert!(payload.is_command()); + match payload.payload { + Some(Payload::Command(cmd)) => { + assert_eq!(cmd.len(), 10_000); + } + _ => panic!("Expected Command payload"), + } +} + +#[test] +fn test_entry_payload_mutually_exclusive() { + let cmd = EntryPayload::command(Bytes::from("data")); + assert!(cmd.is_command()); + assert!(!cmd.is_noop()); + assert!(!cmd.is_config()); + + let noop = EntryPayload::noop(); + assert!(!noop.is_command()); + assert!(noop.is_noop()); + assert!(!noop.is_config()); + + let remove_node = RemoveNode { node_id: 1 }; + let change = Change::RemoveNode(remove_node); + let cfg = EntryPayload::config(change); + assert!(!cfg.is_command()); + assert!(!cfg.is_noop()); + assert!(cfg.is_config()); +} + +#[test] +fn test_entry_payload_command_string() { + let command = "set key value"; + let payload = EntryPayload::command(Bytes::from(command)); + + assert!(payload.is_command()); + match payload.payload { + Some(Payload::Command(cmd)) => { + assert_eq!(cmd.as_ref(), command.as_bytes()); + } + _ => panic!("Expected Command payload"), + } +} + +#[test] +fn test_entry_payload_command_binary() { + let binary_data = vec![0xFF, 0xEE, 0xDD, 0xCC, 0xBB]; + let payload = EntryPayload::command(Bytes::copy_from_slice(&binary_data)); + + assert!(payload.is_command()); + match payload.payload { + Some(Payload::Command(cmd)) => { + assert_eq!(cmd.as_ref(), binary_data.as_slice()); + } + _ => panic!("Expected Command payload"), + } +} diff --git a/d-engine-proto/src/exts/mod.rs b/d-engine-proto/src/exts/mod.rs index 85fb2731..bb2a00ce 100644 --- a/d-engine-proto/src/exts/mod.rs +++ b/d-engine-proto/src/exts/mod.rs @@ -9,3 +9,14 @@ pub mod cluster_ext; pub mod common_ext; pub mod replication_ext; pub mod storage_ext; + +#[cfg(test)] +mod client_ext_test; +#[cfg(test)] +mod cluster_ext_test; +#[cfg(test)] +mod common_ext_test; +#[cfg(test)] +mod replication_ext_test; +#[cfg(test)] +mod storage_ext_test; diff --git a/d-engine-proto/src/exts/replication_ext_test.rs b/d-engine-proto/src/exts/replication_ext_test.rs new file mode 100644 index 00000000..eda53c71 --- /dev/null +++ b/d-engine-proto/src/exts/replication_ext_test.rs @@ -0,0 +1,145 @@ +use crate::common::LogId; +use crate::server::replication::AppendEntriesResponse; + +#[test] +fn test_append_entries_response_success() { + let log_id = LogId { term: 5, index: 10 }; + let response = AppendEntriesResponse::success(1, 5, Some(log_id)); + + assert_eq!(response.node_id, 1); + assert_eq!(response.term, 5); + assert!(response.is_success()); + assert!(!response.is_conflict()); + assert!(!response.is_higher_term()); + + match &response.result { + Some(crate::server::replication::append_entries_response::Result::Success(success)) => { + assert_eq!(success.last_match, Some(log_id)); + } + _ => panic!("Expected success result"), + } +} + +#[test] +fn test_append_entries_response_success_without_log_id() { + let response = AppendEntriesResponse::success(2, 3, None); + + assert_eq!(response.node_id, 2); + assert_eq!(response.term, 3); + assert!(response.is_success()); + assert!(!response.is_conflict()); + assert!(!response.is_higher_term()); + + match &response.result { + Some(crate::server::replication::append_entries_response::Result::Success(success)) => { + assert_eq!(success.last_match, None); + } + _ => panic!("Expected success result"), + } +} + +#[test] +fn test_append_entries_response_conflict() { + let response = AppendEntriesResponse::conflict(3, 6, Some(4), Some(8)); + + assert_eq!(response.node_id, 3); + assert_eq!(response.term, 6); + assert!(!response.is_success()); + assert!(response.is_conflict()); + assert!(!response.is_higher_term()); + + match &response.result { + Some(crate::server::replication::append_entries_response::Result::Conflict(conflict)) => { + assert_eq!(conflict.conflict_term, Some(4)); + assert_eq!(conflict.conflict_index, Some(8)); + } + _ => panic!("Expected conflict result"), + } +} + +#[test] +fn test_append_entries_response_conflict_without_details() { + let response = AppendEntriesResponse::conflict(4, 7, None, None); + + assert_eq!(response.node_id, 4); + assert_eq!(response.term, 7); + assert!(!response.is_success()); + assert!(response.is_conflict()); + assert!(!response.is_higher_term()); + + match &response.result { + Some(crate::server::replication::append_entries_response::Result::Conflict(conflict)) => { + assert_eq!(conflict.conflict_term, None); + assert_eq!(conflict.conflict_index, None); + } + _ => panic!("Expected conflict result"), + } +} + +#[test] +fn test_append_entries_response_higher_term() { + let response = AppendEntriesResponse::higher_term(5, 10); + + assert_eq!(response.node_id, 5); + assert_eq!(response.term, 10); + assert!(!response.is_success()); + assert!(!response.is_conflict()); + assert!(response.is_higher_term()); + + match &response.result { + Some(crate::server::replication::append_entries_response::Result::HigherTerm(term)) => { + assert_eq!(*term, 10); + } + _ => panic!("Expected higher term result"), + } +} + +#[test] +fn test_append_entries_response_mutually_exclusive() { + // Test success response + let success = AppendEntriesResponse::success(1, 1, None); + assert!(success.is_success()); + assert!(!success.is_conflict()); + assert!(!success.is_higher_term()); + + // Test conflict response + let conflict = AppendEntriesResponse::conflict(2, 2, None, None); + assert!(!conflict.is_success()); + assert!(conflict.is_conflict()); + assert!(!conflict.is_higher_term()); + + // Test higher term response + let higher = AppendEntriesResponse::higher_term(3, 3); + assert!(!higher.is_success()); + assert!(!higher.is_conflict()); + assert!(higher.is_higher_term()); +} + +#[test] +fn test_append_entries_response_large_indices() { + let log_id = LogId { + term: u64::MAX, + index: u64::MAX, + }; + let response = AppendEntriesResponse::success(u32::MAX, u64::MAX, Some(log_id)); + + assert_eq!(response.node_id, u32::MAX); + assert_eq!(response.term, u64::MAX); + + match &response.result { + Some(crate::server::replication::append_entries_response::Result::Success(success)) => { + assert_eq!(success.last_match.unwrap().term, u64::MAX); + assert_eq!(success.last_match.unwrap().index, u64::MAX); + } + _ => panic!("Expected success result"), + } +} + +#[test] +fn test_append_entries_response_zero_values() { + let response = AppendEntriesResponse::success(0, 0, None); + + assert_eq!(response.node_id, 0); + assert_eq!(response.term, 0); + assert!(response.is_success()); +} diff --git a/d-engine-proto/src/exts/storage_ext_test.rs b/d-engine-proto/src/exts/storage_ext_test.rs new file mode 100644 index 00000000..f8aade67 --- /dev/null +++ b/d-engine-proto/src/exts/storage_ext_test.rs @@ -0,0 +1,96 @@ +use crate::server::storage::SnapshotMetadata; +use bytes::Bytes; + +#[test] +fn test_snapshot_metadata_checksum_array_valid() { + let checksum = [1u8; 32]; + let metadata = SnapshotMetadata { + checksum: Bytes::copy_from_slice(&checksum), + ..Default::default() + }; + + let result = metadata.checksum_array(); + assert!(result.is_ok()); + let array = result.unwrap(); + assert_eq!(array, checksum); +} + +#[test] +fn test_snapshot_metadata_checksum_array_invalid_length() { + let metadata = SnapshotMetadata { + checksum: Bytes::from(vec![1u8; 16]), // Wrong length + ..Default::default() + }; + + let result = metadata.checksum_array(); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid checksum length: 16" + ); +} + +#[test] +fn test_snapshot_metadata_checksum_array_empty() { + let metadata = SnapshotMetadata { + checksum: Bytes::new(), + ..Default::default() + }; + + let result = metadata.checksum_array(); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid checksum length: 0" + ); +} + +#[test] +fn test_snapshot_metadata_set_checksum_array() { + let mut metadata = SnapshotMetadata::default(); + let checksum = [42u8; 32]; + + metadata.set_checksum_array(checksum); + + assert_eq!(metadata.checksum.as_ref(), &checksum); +} + +#[test] +fn test_snapshot_metadata_round_trip() { + let mut metadata = SnapshotMetadata::default(); + let original_checksum = [123u8; 32]; + + metadata.set_checksum_array(original_checksum); + let retrieved = metadata.checksum_array().unwrap(); + + assert_eq!(retrieved, original_checksum); +} + +#[test] +fn test_snapshot_metadata_checksum_too_long() { + let metadata = SnapshotMetadata { + checksum: Bytes::from(vec![1u8; 64]), // Too long + ..Default::default() + }; + + let result = metadata.checksum_array(); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid checksum length: 64" + ); +} + +#[test] +fn test_snapshot_metadata_checksum_with_different_values() { + let mut metadata = SnapshotMetadata::default(); + let checksum: [u8; 32] = std::array::from_fn(|i| (i as u8).wrapping_mul(7)); + + metadata.set_checksum_array(checksum); + let retrieved = metadata.checksum_array().unwrap(); + + assert_eq!(retrieved, checksum); + for (i, &value) in retrieved.iter().enumerate() { + assert_eq!(value, (i as u8).wrapping_mul(7)); + } +} From bcec0fbb290ea4803874af93f8e9f12d69b47f8e Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Sat, 1 Nov 2025 20:35:38 +0800 Subject: [PATCH 20/26] feat #167: continue optimization pub use * --- .github/workflows/ci.yml | 24 +- Cargo.lock | 22 +- Cargo.toml | 5 +- Makefile | 4 +- README.md | 2 +- benches/d-engine-bench/Cargo.lock | 714 +++--------------- benches/d-engine-bench/Cargo.toml | 3 +- benches/d-engine-bench/src/main.rs | 12 +- d-engine-docs/Cargo.toml | 2 +- d-engine-docs/src/docs/overview.md | 6 +- d-engine-proto/src/exts/mod.rs | 2 +- d-engine-proto/src/lib.rs | 2 +- d-engine-runtime/src/lib.rs | 26 - d-engine-runtime/src/test_utils/mock/mod.rs | 40 - d-engine-runtime/src/test_utils/mod.rs | 5 - .../Cargo.toml | 4 +- d-engine-server/src/lib.rs | 114 +++ .../src/membership/membership_guard.rs | 0 .../src/membership/membership_guard_test.rs | 0 .../src/membership/mod.rs | 0 .../src/membership/raft_membership.rs | 16 +- .../src/membership/raft_membership_test.rs | 4 +- .../src/network/connection_cache.rs | 0 .../src/network/connection_cache_test.rs | 2 +- .../src/network/grpc/grpc_raft_service.rs | 0 .../network/grpc/grpc_raft_service_test.rs | 0 .../src/network/grpc/grpc_transport.rs | 2 +- .../src/network/grpc/grpc_transport_test.rs | 2 +- .../src/network/grpc/mod.rs | 0 .../src/network/health_checker.rs | 2 +- .../src/network/health_checker_test.rs | 0 .../src/network/health_monitor.rs | 0 .../src/network/health_monitor_test.rs | 0 .../src/network/mod.rs | 0 .../src/network/network_test.rs | 0 .../src/node/builder.rs | 39 +- .../src/node/builder_test.rs | 4 +- .../src/node/mod.rs | 14 +- .../src/node/node_test.rs | 0 .../src/node/type_config/mod.rs | 0 .../src/node/type_config/raft_type_config.rs | 6 +- .../storage/adaptors/file/file_engine_test.rs | 0 .../adaptors/file/file_state_machine.rs | 0 .../adaptors/file/file_state_machine_test.rs | 0 .../adaptors/file/file_storage_engine.rs | 19 +- .../adaptors/file/file_storage_engine_test.rs | 0 .../src/storage/adaptors/file/mod.rs | 0 .../src/storage/adaptors/mod.rs | 0 .../src/storage/adaptors/rocksdb/mod.rs | 0 .../adaptors/rocksdb/rocksdb_engine_test.rs | 0 .../adaptors/rocksdb/rocksdb_state_machine.rs | 0 .../rocksdb/rocksdb_storage_engine.rs | 21 +- .../src/storage/buffered/buffered_raft_log.rs | 34 +- .../buffered/buffered_raft_log_test.rs | 4 +- .../src/storage/buffered/mod.rs | 0 .../src/storage/mod.rs | 0 .../src/test_utils/integration/mod.rs | 140 +++- .../src/test_utils/mock/mock_node_builder.rs | 140 +++- .../src/test_utils/mock/mock_rpc_services.rs | 0 d-engine-server/src/test_utils/mock/mod.rs | 87 +++ d-engine-server/src/test_utils/mod.rs | 28 + .../src/utils/async_task.rs | 0 .../src/utils/async_task_test.rs | 4 +- .../src/utils/cluster.rs | 0 .../src/utils/mod.rs | 3 - .../src/utils/net.rs | 0 .../src/utils/net_test.rs | 2 +- .../src/utils/scoped_timer.rs | 0 .../src/utils/utils_test.rs | 2 +- .../components/buffered_raft_log_test.rs | 18 +- .../election/election_handler_test.rs | 2 +- .../tests/components/election/mod.rs | 0 .../tests/components/mod.rs | 0 .../raft_role/candidate_state_test.rs | 4 +- .../raft_role/follower_state_test.rs | 3 +- .../components/raft_role/leader_state_test.rs | 2 +- .../raft_role/learner_state_test.rs | 0 .../tests/components/raft_role/mod.rs | 0 .../tests/components/raft_test.rs | 2 +- .../tests/components/replication/mod.rs | 0 .../replication/replication_handler_test.rs | 10 +- .../tests/integration_test.rs | 0 examples/rocksdb-cluster/Cargo.lock | 59 +- examples/rocksdb-cluster/Cargo.toml | 2 +- examples/rocksdb-cluster/src/main.rs | 6 +- examples/sled-cluster/Cargo.toml | 2 +- examples/sled-cluster/src/main.rs | 3 +- examples/sled-cluster/src/sled_engine_test.rs | 18 +- .../sled-cluster/src/sled_state_machine.rs | 28 +- .../sled-cluster/src/sled_storage_engine.rs | 20 +- .../state_machine/sled_state_machine_test.rs | 8 +- .../sled_storage_engine_test.rs | 8 +- examples/three-nodes-cluster/Cargo.toml | 2 +- examples/three-nodes-cluster/src/main.rs | 21 +- 94 files changed, 859 insertions(+), 921 deletions(-) delete mode 100644 d-engine-runtime/src/lib.rs delete mode 100644 d-engine-runtime/src/test_utils/mock/mod.rs delete mode 100644 d-engine-runtime/src/test_utils/mod.rs rename {d-engine-runtime => d-engine-server}/Cargo.toml (94%) create mode 100644 d-engine-server/src/lib.rs rename {d-engine-runtime => d-engine-server}/src/membership/membership_guard.rs (100%) rename {d-engine-runtime => d-engine-server}/src/membership/membership_guard_test.rs (100%) rename {d-engine-runtime => d-engine-server}/src/membership/mod.rs (100%) rename {d-engine-runtime => d-engine-server}/src/membership/raft_membership.rs (98%) rename {d-engine-runtime => d-engine-server}/src/membership/raft_membership_test.rs (99%) rename {d-engine-runtime => d-engine-server}/src/network/connection_cache.rs (100%) rename {d-engine-runtime => d-engine-server}/src/network/connection_cache_test.rs (99%) rename {d-engine-runtime => d-engine-server}/src/network/grpc/grpc_raft_service.rs (100%) rename {d-engine-runtime => d-engine-server}/src/network/grpc/grpc_raft_service_test.rs (100%) rename {d-engine-runtime => d-engine-server}/src/network/grpc/grpc_transport.rs (99%) rename {d-engine-runtime => d-engine-server}/src/network/grpc/grpc_transport_test.rs (99%) rename {d-engine-runtime => d-engine-server}/src/network/grpc/mod.rs (100%) rename {d-engine-runtime => d-engine-server}/src/network/health_checker.rs (98%) rename {d-engine-runtime => d-engine-server}/src/network/health_checker_test.rs (100%) rename {d-engine-runtime => d-engine-server}/src/network/health_monitor.rs (100%) rename {d-engine-runtime => d-engine-server}/src/network/health_monitor_test.rs (100%) rename {d-engine-runtime => d-engine-server}/src/network/mod.rs (100%) rename {d-engine-runtime => d-engine-server}/src/network/network_test.rs (100%) rename {d-engine-runtime => d-engine-server}/src/node/builder.rs (96%) rename {d-engine-runtime => d-engine-server}/src/node/builder_test.rs (99%) rename {d-engine-runtime => d-engine-server}/src/node/mod.rs (93%) rename {d-engine-runtime => d-engine-server}/src/node/node_test.rs (100%) rename {d-engine-runtime => d-engine-server}/src/node/type_config/mod.rs (100%) rename {d-engine-runtime => d-engine-server}/src/node/type_config/raft_type_config.rs (88%) rename {d-engine-runtime => d-engine-server}/src/storage/adaptors/file/file_engine_test.rs (100%) rename {d-engine-runtime => d-engine-server}/src/storage/adaptors/file/file_state_machine.rs (100%) rename {d-engine-runtime => d-engine-server}/src/storage/adaptors/file/file_state_machine_test.rs (100%) rename {d-engine-runtime => d-engine-server}/src/storage/adaptors/file/file_storage_engine.rs (96%) rename {d-engine-runtime => d-engine-server}/src/storage/adaptors/file/file_storage_engine_test.rs (100%) rename {d-engine-runtime => d-engine-server}/src/storage/adaptors/file/mod.rs (100%) rename {d-engine-runtime => d-engine-server}/src/storage/adaptors/mod.rs (100%) rename {d-engine-runtime => d-engine-server}/src/storage/adaptors/rocksdb/mod.rs (100%) rename {d-engine-runtime => d-engine-server}/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs (100%) rename {d-engine-runtime => d-engine-server}/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs (100%) rename {d-engine-runtime => d-engine-server}/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs (96%) rename {d-engine-runtime => d-engine-server}/src/storage/buffered/buffered_raft_log.rs (98%) rename {d-engine-runtime => d-engine-server}/src/storage/buffered/buffered_raft_log_test.rs (99%) rename {d-engine-runtime => d-engine-server}/src/storage/buffered/mod.rs (100%) rename {d-engine-runtime => d-engine-server}/src/storage/mod.rs (100%) rename {d-engine-runtime => d-engine-server}/src/test_utils/integration/mod.rs (63%) rename {d-engine-runtime => d-engine-server}/src/test_utils/mock/mock_node_builder.rs (75%) rename {d-engine-runtime => d-engine-server}/src/test_utils/mock/mock_rpc_services.rs (100%) create mode 100644 d-engine-server/src/test_utils/mock/mod.rs create mode 100644 d-engine-server/src/test_utils/mod.rs rename {d-engine-runtime => d-engine-server}/src/utils/async_task.rs (100%) rename {d-engine-runtime => d-engine-server}/src/utils/async_task_test.rs (97%) rename {d-engine-runtime => d-engine-server}/src/utils/cluster.rs (100%) rename {d-engine-runtime => d-engine-server}/src/utils/mod.rs (82%) rename {d-engine-runtime => d-engine-server}/src/utils/net.rs (100%) rename {d-engine-runtime => d-engine-server}/src/utils/net_test.rs (94%) rename {d-engine-runtime => d-engine-server}/src/utils/scoped_timer.rs (100%) rename {d-engine-runtime => d-engine-server}/src/utils/utils_test.rs (97%) rename {d-engine-runtime => d-engine-server}/tests/components/buffered_raft_log_test.rs (99%) rename {d-engine-runtime => d-engine-server}/tests/components/election/election_handler_test.rs (99%) rename {d-engine-runtime => d-engine-server}/tests/components/election/mod.rs (100%) rename {d-engine-runtime => d-engine-server}/tests/components/mod.rs (100%) rename {d-engine-runtime => d-engine-server}/tests/components/raft_role/candidate_state_test.rs (99%) rename {d-engine-runtime => d-engine-server}/tests/components/raft_role/follower_state_test.rs (99%) rename {d-engine-runtime => d-engine-server}/tests/components/raft_role/leader_state_test.rs (99%) rename {d-engine-runtime => d-engine-server}/tests/components/raft_role/learner_state_test.rs (100%) rename {d-engine-runtime => d-engine-server}/tests/components/raft_role/mod.rs (100%) rename {d-engine-runtime => d-engine-server}/tests/components/raft_test.rs (99%) rename {d-engine-runtime => d-engine-server}/tests/components/replication/mod.rs (100%) rename {d-engine-runtime => d-engine-server}/tests/components/replication/replication_handler_test.rs (99%) rename {d-engine-runtime => d-engine-server}/tests/integration_test.rs (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3f865fa..7d542ed8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,32 +38,28 @@ jobs: name: Test and Collect Coverage runs-on: ubuntu-latest needs: lint + env: + RUST_TOOLCHAIN: 1.88.0 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Cache dependencies - uses: actions/cache@v4 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - ~/.cargo/bin - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo- + toolchain: ${{ env.RUST_TOOLCHAIN }} + components: llvm-tools-preview + + - name: Cache using Swatinem/rust-cache + uses: Swatinem/rust-cache@v2 - name: Install Protobuf compiler run: sudo apt-get install -y protobuf-compiler - - name: Install cargo-llvm-cov + - name: Install cargo tools run: | cargo install cargo-llvm-cov --locked - - - name: Install cargo-nextest - run: | cargo install cargo-nextest --locked - name: Run tests with nextest and generate coverage diff --git a/Cargo.lock b/Cargo.lock index 512d0f60..f2bc3c9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,9 +22,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -239,9 +239,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.43" +version = "1.2.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ "find-msvc-tools", "jobserver", @@ -469,7 +469,7 @@ dependencies = [ name = "d-engine-docs" version = "0.2.0" dependencies = [ - "d-engine-runtime", + "d-engine-server", "tokio", "tracing", ] @@ -488,7 +488,7 @@ dependencies = [ ] [[package]] -name = "d-engine-runtime" +name = "d-engine-server" version = "0.2.0" dependencies = [ "arc-swap", @@ -501,7 +501,7 @@ dependencies = [ "crossbeam-skiplist", "d-engine-core", "d-engine-proto", - "d-engine-runtime", + "d-engine-server", "dashmap", "futures", "http-body", @@ -1655,9 +1655,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.7" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "ring", "rustls-pki-types", @@ -2260,9 +2260,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "untrusted" diff --git a/Cargo.toml b/Cargo.toml index fee3a640..70f3f9f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ members = [ "d-engine-proto", "d-engine-core", "d-engine-client", - "d-engine-runtime", "d-engine-docs", + "d-engine-server", ] exclude = [ @@ -13,6 +13,7 @@ exclude = [ "examples/rocksdb-cluster", "examples/sled-cluster", "examples/three-nodes-cluster", + "benches/d-engine-bench", ] resolver = "2" @@ -21,7 +22,7 @@ resolver = "2" d-engine-proto = { path = "./d-engine-proto" } d-engine-core = { path = "./d-engine-core" } d-engine-client = { path = "./d-engine-client" } -d-engine-runtime = { path = "./d-engine-runtime" } +d-engine-server = { path = "./d-engine-server" } tokio = { version = "1.4", features = [ "macros", diff --git a/Makefile b/Makefile index 7721736a..0cd17e83 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ RUST_LOG_LEVEL ?= d_engine=debug RUST_BACKTRACE ?= 1 # Workspace member crates -WORKSPACE_MEMBERS := d-engine-proto d-engine-core d-engine-client d-engine-runtime d-engine-docs +WORKSPACE_MEMBERS := d-engine-proto d-engine-core d-engine-client d-engine-server d-engine-docs # Color codes for formatted output RED := \033[0;31m @@ -252,7 +252,7 @@ test-examples: install-tools check-workspace $(CARGO) test --workspace --examples --no-fail-fast -- --test-threads=1 --nocapture @echo "$(GREEN)✓ Examples tests passed$(NC)" -## test-crate Run tests for a specific crate (usage: make test-crate CRATE=d-engine-runtime) +## test-crate Run tests for a specific crate (usage: make test-crate CRATE=d-engine) test-crate: install-tools check-workspace ifndef CRATE @echo "$(RED)Error: CRATE variable not set. Usage: make test-crate CRATE=crate-name$(NC)" diff --git a/README.md b/README.md index 560c9f87..f99cea0d 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ d-engine = { version = "0.1.4", features = ["rocksdb"] } use d-engine::{RaftCore, MemoryStorage, Config}; ```rust -use d_engine::{NodeBuilder, FileStorageEngine, FileStateMachine}; +use d_engine_server::{NodeBuilder, FileStorageEngine, FileStateMachine}; use tokio::sync::watch; use std::sync::Arc; use std::path::PathBuf; diff --git a/benches/d-engine-bench/Cargo.lock b/benches/d-engine-bench/Cargo.lock index 83c3d158..7d66d618 100644 --- a/benches/d-engine-bench/Cargo.lock +++ b/benches/d-engine-bench/Cargo.lock @@ -2,48 +2,21 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "anstream" version = "0.6.21" @@ -106,19 +79,6 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" -[[package]] -name = "async-compression" -version = "0.4.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" -dependencies = [ - "compression-codecs", - "compression-core", - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -220,21 +180,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "backtrace" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link", -] - [[package]] name = "base64" version = "0.21.7" @@ -247,35 +192,11 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bitflags" -version = "1.3.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "byteorder" @@ -294,9 +215,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.41" +version = "1.2.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ "find-msvc-tools", "shlex", @@ -304,15 +225,15 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.48" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", "clap_derive", @@ -320,9 +241,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstream", "anstyle", @@ -332,9 +253,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.47" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -344,9 +265,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "colorchoice" @@ -354,44 +275,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" -[[package]] -name = "compression-codecs" -version = "0.4.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" -dependencies = [ - "compression-core", - "flate2", - "memchr", -] - -[[package]] -name = "compression-core" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" - -[[package]] -name = "config" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" -dependencies = [ - "nom", - "pathdiff", - "serde", - "toml", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - [[package]] name = "crc32fast" version = "1.5.0" @@ -401,19 +284,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -423,44 +293,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-skiplist" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -468,104 +300,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +name = "d-engine-bench" +version = "0.1.4" dependencies = [ - "generic-array", - "typenum", + "atomic-shim", + "bytes", + "clap", + "d-engine-client", + "d-engine-proto", + "futures", + "hdrhistogram", + "rand", + "thiserror", + "tokio", ] [[package]] -name = "d-engine" -version = "0.1.3" +name = "d-engine-client" +version = "0.2.0" dependencies = [ "arc-swap", - "async-compression", - "async-stream", - "bincode", "bytes", - "config", - "crc32fast", - "crossbeam", - "crossbeam-skiplist", - "dashmap", - "flate2", + "d-engine-proto", "futures", - "http-body", - "http-body-util", - "lru", - "memmap2", - "metrics", - "nanoid", - "parking_lot", - "prost", "rand", - "rcgen", - "ring", "serde", - "sha2", - "tempfile", - "thiserror", "tokio", "tokio-stream", - "tokio-tar", - "tokio-util", "tonic", - "tonic-build", "tonic-health", "tracing", - "vergen", ] [[package]] -name = "d-engine-bench" -version = "0.1.4" +name = "d-engine-proto" +version = "0.2.0" dependencies = [ - "atomic-shim", "bytes", - "clap", - "d-engine", - "futures", - "hdrhistogram", - "rand", - "thiserror", - "tokio", -] - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "prost", + "serde", + "tonic", + "tonic-build", + "tracing", + "vergen", ] [[package]] name = "deranged" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", ] -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - [[package]] name = "either" version = "1.15.0" @@ -594,18 +382,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "filetime" -version = "0.2.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.60.2", -] - [[package]] name = "find-msvc-tools" version = "0.1.4" @@ -620,12 +396,11 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", - "libz-sys", "miniz_oxide", ] @@ -635,12 +410,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "futures" version = "0.3.31" @@ -730,16 +499,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dc8f7d2ded5f9209535e4b3fd4d39c002f30902ff5ce9f64e2c33d549576500" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getrandom" version = "0.2.16" @@ -748,27 +507,21 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", ] -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - [[package]] name = "h2" version = "0.4.12" @@ -781,7 +534,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.11.4", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -794,23 +547,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - [[package]] name = "hashbrown" version = "0.16.0" @@ -952,30 +688,19 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", ] -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -998,28 +723,6 @@ version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" -[[package]] -name = "libredox" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" -dependencies = [ - "bitflags 2.9.4", - "libc", - "redox_syscall 0.5.18", -] - -[[package]] -name = "libz-sys" -version = "1.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -1041,15 +744,6 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" -[[package]] -name = "lru" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0281c2e25e62316a5c9d98f2d2e9e95a37841afdaf4383c177dbb5c1dfab0568" -dependencies = [ - "hashbrown 0.15.5", -] - [[package]] name = "matchit" version = "0.7.3" @@ -1062,25 +756,6 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" -[[package]] -name = "memmap2" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" -dependencies = [ - "libc", -] - -[[package]] -name = "metrics" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dea7ac8057892855ec285c440160265225438c3c45072613c25a4b26e98ef5" -dependencies = [ - "ahash", - "portable-atomic", -] - [[package]] name = "mime" version = "0.3.17" @@ -1105,13 +780,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -1120,15 +795,6 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand", -] - [[package]] name = "nom" version = "7.1.3" @@ -1163,15 +829,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -1180,9 +837,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "parking_lot" @@ -1202,27 +859,11 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "pem" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64 0.22.1", - "serde_core", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -1236,7 +877,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.11.4", + "indexmap 2.12.0", ] [[package]] @@ -1271,18 +912,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "portable-atomic" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" - [[package]] name = "powerfmt" version = "0.2.0" @@ -1310,9 +939,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -1414,42 +1043,20 @@ dependencies = [ "getrandom 0.2.16", ] -[[package]] -name = "rcgen" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" -dependencies = [ - "pem", - "ring", - "rustls-pki-types", - "time", - "yasna", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags", ] [[package]] name = "regex" -version = "1.12.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -1459,9 +1066,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1470,9 +1077,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "ring" @@ -1488,19 +1095,13 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - [[package]] name = "rustix" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.4", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1509,9 +1110,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.32" +version = "0.23.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" dependencies = [ "log", "once_cell", @@ -1533,18 +1134,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.7" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "ring", "rustls-pki-types", @@ -1593,26 +1194,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "shlex" version = "1.3.0" @@ -1680,9 +1261,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -1702,7 +1283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2", @@ -1763,29 +1344,26 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", "socket2 0.6.1", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -1813,21 +1391,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-tar" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" -dependencies = [ - "filetime", - "futures-core", - "libc", - "redox_syscall 0.3.5", - "tokio", - "tokio-stream", - "xattr", -] - [[package]] name = "tokio-util" version = "0.7.16" @@ -1841,47 +1404,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap 2.11.4", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "tonic" version = "0.12.3" @@ -2025,17 +1547,11 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "untrusted" @@ -2049,12 +1565,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "vergen" version = "8.3.2" @@ -2067,12 +1577,6 @@ dependencies = [ "time", ] -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "want" version = "0.3.1" @@ -2088,15 +1592,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -2121,15 +1616,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" @@ -2277,40 +1763,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" -[[package]] -name = "winnow" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" -dependencies = [ - "memchr", -] - [[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" -[[package]] -name = "xattr" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" -dependencies = [ - "libc", - "rustix", -] - -[[package]] -name = "yasna" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" -dependencies = [ - "time", -] - [[package]] name = "zerocopy" version = "0.8.27" diff --git a/benches/d-engine-bench/Cargo.toml b/benches/d-engine-bench/Cargo.toml index b547ff12..78a53346 100644 --- a/benches/d-engine-bench/Cargo.toml +++ b/benches/d-engine-bench/Cargo.toml @@ -4,7 +4,8 @@ version = "0.1.4" edition = "2021" [dependencies] -d-engine = { path = "../../../d-engine" } +d-engine-client = { path = "../../d-engine-client" } +d-engine-proto = { path = "../../d-engine-proto" } # d-engine = { git = "https://github.com/deventlab/d-engine.git", branch = "feature/79-snapshot" } tokio = { version = "1.44", features = ["full"] } diff --git a/benches/d-engine-bench/src/main.rs b/benches/d-engine-bench/src/main.rs index b653c0f8..a17e3e49 100644 --- a/benches/d-engine-bench/src/main.rs +++ b/benches/d-engine-bench/src/main.rs @@ -7,8 +7,10 @@ use std::time::Instant; use clap::Parser; use clap::Subcommand; -use d_engine::proto::client::ReadConsistencyPolicy; -use d_engine::ClientBuilder; +use d_engine_client::Client; +use d_engine_client::ClientApiError; +use d_engine_client::ClientBuilder; +use d_engine_proto::client::ReadConsistencyPolicy; use hdrhistogram::Histogram; use rand::distributions::Alphanumeric; use rand::Rng; @@ -53,7 +55,7 @@ enum Commands { } struct ClientPool { - clients: Vec>, + clients: Vec>, counter: AtomicU64, } @@ -70,7 +72,7 @@ impl ClientPool { async fn new( endpoints: Vec, pool_size: usize, - ) -> Result { + ) -> Result { let mut clients = Vec::with_capacity(pool_size); for _ in 0..pool_size { let client = ClientBuilder::new(endpoints.clone()) @@ -87,7 +89,7 @@ impl ClientPool { }) } - fn next(&self) -> Arc { + fn next(&self) -> Arc { let idx = self.counter.fetch_add(1, Ordering::Relaxed); let len = self.clients.len() as u64; self.clients[(idx % len) as usize].clone() diff --git a/d-engine-docs/Cargo.toml b/d-engine-docs/Cargo.toml index 07c45023..5607f2b0 100644 --- a/d-engine-docs/Cargo.toml +++ b/d-engine-docs/Cargo.toml @@ -4,7 +4,7 @@ version = "0.2.0" edition = "2024" [dependencies] -d-engine-runtime = { workspace = true } +d-engine-server = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/d-engine-docs/src/docs/overview.md b/d-engine-docs/src/docs/overview.md index 05eecc9c..1e771cf7 100644 --- a/d-engine-docs/src/docs/overview.md +++ b/d-engine-docs/src/docs/overview.md @@ -25,9 +25,9 @@ ## Quick Start ```no_run -use d_engine_runtime::NodeBuilder; -use d_engine_runtime::FileStorageEngine; -use d_engine_runtime::FileStateMachine; +use d_engine::NodeBuilder; +use d_engine::FileStorageEngine; +use d_engine::FileStateMachine; use tokio::sync::watch; use tracing::error; use tracing::info; diff --git a/d-engine-proto/src/exts/mod.rs b/d-engine-proto/src/exts/mod.rs index bb2a00ce..ab8a6fbb 100644 --- a/d-engine-proto/src/exts/mod.rs +++ b/d-engine-proto/src/exts/mod.rs @@ -2,7 +2,7 @@ //! //! This module provides additional trait implementations and helper methods //! for protobuf-generated types. While the imports appear unused at the module level, -//! they are re-exported for use by dependent crates (d-engine-core, d-engine-runtime, d-engine-client). +//! they are re-exported for use by dependent crates (d-engine-core, d-engine, d-engine-client). pub mod client_ext; pub mod cluster_ext; diff --git a/d-engine-proto/src/lib.rs b/d-engine-proto/src/lib.rs index 09f0b170..f8068e77 100644 --- a/d-engine-proto/src/lib.rs +++ b/d-engine-proto/src/lib.rs @@ -2,7 +2,7 @@ //! //! This module provides additional trait implementations and helper methods //! for protobuf-generated types. The re-exports here are used by dependent crates -//! (d-engine-core, d-engine-runtime, d-engine-client) when they import from this crate. +//! (d-engine-core, d-engine, d-engine-client) when they import from this crate. pub mod common { include!("generated/d_engine.common.rs"); diff --git a/d-engine-runtime/src/lib.rs b/d-engine-runtime/src/lib.rs deleted file mode 100644 index bb609ba2..00000000 --- a/d-engine-runtime/src/lib.rs +++ /dev/null @@ -1,26 +0,0 @@ -#![doc = include_str!("../../d-engine-docs/src/docs/overview.md")] - -// #![warn(missing_docs)] - -pub mod node; -pub mod storage; - -#[doc(hidden)] -pub use node::*; - -mod membership; -mod network; - -pub(crate) use membership::*; -pub(crate) use network::*; -pub use storage::*; - -#[doc(hidden)] -pub mod utils; -#[doc(hidden)] -pub use utils::*; - -#[cfg(any(test, feature = "test-utils"))] -pub mod test_utils; -#[cfg(any(test, feature = "test-utils"))] -pub use test_utils::*; diff --git a/d-engine-runtime/src/test_utils/mock/mod.rs b/d-engine-runtime/src/test_utils/mock/mod.rs deleted file mode 100644 index eb2c5b20..00000000 --- a/d-engine-runtime/src/test_utils/mock/mod.rs +++ /dev/null @@ -1,40 +0,0 @@ -mod mock_node_builder; -// mod mock_rpc_services; - -pub use mock_node_builder::*; -// pub use mock_rpc_services::*; - -use crate::Node; -use d_engine_core::node_config; -use d_engine_core::{MockTypeConfig, Raft}; -use d_engine_proto::server::cluster::NodeMeta; -use tokio::sync::watch; - -pub fn mock_node( - db_path: &str, - shutdown_signal: watch::Receiver<()>, - peers_meta_option: Option>, -) -> Node { - let mut node_config = node_config(db_path); - if let Some(peers_meta) = peers_meta_option { - node_config.cluster.initial_cluster = peers_meta; - } - // Initializing Shutdown Signal - // let (graceful_tx, graceful_rx) = watch::channel(()); - MockBuilder::new(shutdown_signal).with_node_config(node_config).build_node() -} - -pub fn mock_raft( - db_path: &str, - shutdown_signal: watch::Receiver<()>, - peers_meta_option: Option>, -) -> Raft { - let mut node_config = node_config(db_path); - // Set batch_threshold=0, means the replication will be triggered immediatelly. - node_config.raft.replication.rpc_append_entries_in_batch_threshold = 0; - if let Some(peers_meta) = peers_meta_option { - node_config.cluster.initial_cluster = peers_meta; - } - - MockBuilder::new(shutdown_signal).with_node_config(node_config).build_raft() -} diff --git a/d-engine-runtime/src/test_utils/mod.rs b/d-engine-runtime/src/test_utils/mod.rs deleted file mode 100644 index e7b56a44..00000000 --- a/d-engine-runtime/src/test_utils/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod integration; -mod mock; - -pub use integration::*; -pub use mock::*; diff --git a/d-engine-runtime/Cargo.toml b/d-engine-server/Cargo.toml similarity index 94% rename from d-engine-runtime/Cargo.toml rename to d-engine-server/Cargo.toml index 89d042e0..910b5477 100644 --- a/d-engine-runtime/Cargo.toml +++ b/d-engine-server/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "d-engine-runtime" +name = "d-engine-server" version = "0.2.0" edition = "2024" @@ -45,7 +45,7 @@ metrics = { version = "0.24", features = [] } [dev-dependencies] -d-engine-runtime = { workspace = true, features = ["test-utils"] } +d-engine-server = { workspace = true, features = ["test-utils"] } d-engine-core = { workspace = true, features = ["test-utils"] } serial_test = "3.2.0" temp-env = "0.3.6" diff --git a/d-engine-server/src/lib.rs b/d-engine-server/src/lib.rs new file mode 100644 index 00000000..11cde340 --- /dev/null +++ b/d-engine-server/src/lib.rs @@ -0,0 +1,114 @@ +//! # d-engine-server +//! +//! Production-ready Raft consensus server implementation. +//! +//! ## Architecture +//! - [`node`] - Node lifecycle management +//! - [`storage`] - Pluggable storage backends +//! - Core Raft protocol (re-exported from `d-engine-core`) +//! - Protocol definitions (re-exported from `d-engine-proto`) +//! +//! ## Quick Start +//! ```no_run +//! use d_engine_server::{NodeBuilder, FileStorageEngine, FileStateMachine}; +//! use std::sync::Arc; +//! use std::path::PathBuf; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { +//! let (tx, rx) = tokio::sync::watch::channel(()); +//! +//! let storage = Arc::new(FileStorageEngine::new(PathBuf::from("/tmp/storage"))?); +//! let state_machine = Arc::new(FileStateMachine::new(PathBuf::from("/tmp/sm")).await?); +//! +//! let node = NodeBuilder::new(None, rx) +//! .storage_engine(storage) +//! .state_machine(state_machine) +//! .build() +//! .start_rpc_server() +//! .await +//! .ready()?; +//! +//! node.run().await?; +//! Ok(()) +//! } +//! ``` +//! +//! ## Custom Storage Backend +//! +//! Implement the [`StateMachine`] and [`StorageEngine`] traits: +//! +//! ```rust,ignore +//! use d_engine_server::{StateMachine, StorageEngine}; +//! +//! struct MyStateMachine { /* ... */ } +//! impl StateMachine for MyStateMachine { /* ... */ } +//! +//! struct MyStorageEngine { /* ... */ } +//! impl StorageEngine for MyStorageEngine { /* ... */ } +//! ``` +//! +//! See the [d-engine-docs](https://docs.rs/d-engine-docs) for architecture details. + +#![doc = include_str!("../../d-engine-docs/src/docs/overview.md")] +#![warn(missing_docs)] + +// ==================== Core Public API ==================== + +/// Node lifecycle management +/// +/// Contains [`Node`] and [`NodeBuilder`] for server setup. +pub mod node; + +/// Storage layer implementations +/// +/// Provides file-based and RocksDB storage backends. +pub mod storage; + +// -------------------- Primary Entry Points -------------------- +pub use node::{Node, NodeBuilder}; + +// Re-export storage implementations +pub use storage::{ + FileStateMachine, + // File-based storage + FileStorageEngine, +}; + +// Conditional RocksDB exports +#[cfg(feature = "rocksdb")] +pub use storage::{RocksDBStateMachine, RocksDBStorageEngine}; + +// -------------------- Extension Traits -------------------- + +/// Storage trait for custom implementations +/// +/// Implement this to create your own storage backend. +pub use d_engine_core::{StateMachine, StorageEngine}; + +// -------------------- Common Types -------------------- + +/// Raft error types +pub use d_engine_core::{Error as RaftError, Result, StorageError}; + +// Protocol types (minimal set for custom storage implementations) +pub use d_engine_proto::common::{Entry, LogId}; + +// ==================== Internal API (Hidden) ==================== +mod membership; +mod network; +mod utils; + +#[doc(hidden)] +pub use d_engine_core::Raft; + +// ==================== Test Utilities ==================== + +/// Test utilities for d-engine-server +/// +/// This module is only available when the `test-utils` feature is enabled +/// or when running tests. +/// +/// Contains mock implementations and test helpers. +#[cfg(any(test, feature = "test-utils"))] +pub mod test_utils; diff --git a/d-engine-runtime/src/membership/membership_guard.rs b/d-engine-server/src/membership/membership_guard.rs similarity index 100% rename from d-engine-runtime/src/membership/membership_guard.rs rename to d-engine-server/src/membership/membership_guard.rs diff --git a/d-engine-runtime/src/membership/membership_guard_test.rs b/d-engine-server/src/membership/membership_guard_test.rs similarity index 100% rename from d-engine-runtime/src/membership/membership_guard_test.rs rename to d-engine-server/src/membership/membership_guard_test.rs diff --git a/d-engine-runtime/src/membership/mod.rs b/d-engine-server/src/membership/mod.rs similarity index 100% rename from d-engine-runtime/src/membership/mod.rs rename to d-engine-server/src/membership/mod.rs diff --git a/d-engine-runtime/src/membership/raft_membership.rs b/d-engine-server/src/membership/raft_membership.rs similarity index 98% rename from d-engine-runtime/src/membership/raft_membership.rs rename to d-engine-server/src/membership/raft_membership.rs index 7534356e..23bc92de 100644 --- a/d-engine-runtime/src/membership/raft_membership.rs +++ b/d-engine-server/src/membership/raft_membership.rs @@ -28,13 +28,13 @@ use tracing::info; use tracing::warn; use super::MembershipGuard; -use crate::ConnectionCache; -use crate::HealthChecker; -use crate::HealthCheckerApis; -use crate::HealthMonitor; -use crate::RaftHealthMonitor; -use crate::async_task::task_with_timeout_and_exponential_backoff; -use crate::net::address_str; +use crate::network::ConnectionCache; +use crate::network::HealthChecker; +use crate::network::HealthCheckerApis; +use crate::network::HealthMonitor; +use crate::network::RaftHealthMonitor; +use crate::utils::async_task::task_with_timeout_and_exponential_backoff; +use crate::utils::net::address_str; use d_engine_core::ConnectionType; use d_engine_core::Membership; use d_engine_core::MembershipError; @@ -769,7 +769,7 @@ impl RaftMembership where T: TypeConfig, { - pub fn new( + pub(crate) fn new( node_id: u32, initial_nodes: Vec, config: RaftNodeConfig, diff --git a/d-engine-runtime/src/membership/raft_membership_test.rs b/d-engine-server/src/membership/raft_membership_test.rs similarity index 99% rename from d-engine-runtime/src/membership/raft_membership_test.rs rename to d-engine-server/src/membership/raft_membership_test.rs index d201245d..0fc7f169 100644 --- a/d-engine-runtime/src/membership/raft_membership_test.rs +++ b/d-engine-server/src/membership/raft_membership_test.rs @@ -7,7 +7,7 @@ use d_engine_core::ConnectionType; use d_engine_core::ConsensusError; use d_engine_core::Error; -use crate::RaftTypeConfig; +use crate::node::RaftTypeConfig; use d_engine_core::Membership; use d_engine_core::MembershipError; use d_engine_core::MockStorageEngine; @@ -1172,7 +1172,7 @@ mod pre_warm_connections_tests { use tracing_test::traced_test; use super::*; - use crate::net::address_str; + use crate::utils::net::address_str; use d_engine_proto::common::NodeStatus; use d_engine_proto::server::cluster::NodeMeta; diff --git a/d-engine-runtime/src/network/connection_cache.rs b/d-engine-server/src/network/connection_cache.rs similarity index 100% rename from d-engine-runtime/src/network/connection_cache.rs rename to d-engine-server/src/network/connection_cache.rs diff --git a/d-engine-runtime/src/network/connection_cache_test.rs b/d-engine-server/src/network/connection_cache_test.rs similarity index 99% rename from d-engine-runtime/src/network/connection_cache_test.rs rename to d-engine-server/src/network/connection_cache_test.rs index 916af55c..d97ebd06 100644 --- a/d-engine-runtime/src/network/connection_cache_test.rs +++ b/d-engine-server/src/network/connection_cache_test.rs @@ -5,7 +5,7 @@ use tokio::sync::oneshot; use tracing_test::traced_test; use super::*; -use crate::net::address_str; +use crate::utils::net::address_str; use d_engine_core::ConnectionParams; use d_engine_core::ConnectionType; use d_engine_core::NetworkConfig; diff --git a/d-engine-runtime/src/network/grpc/grpc_raft_service.rs b/d-engine-server/src/network/grpc/grpc_raft_service.rs similarity index 100% rename from d-engine-runtime/src/network/grpc/grpc_raft_service.rs rename to d-engine-server/src/network/grpc/grpc_raft_service.rs diff --git a/d-engine-runtime/src/network/grpc/grpc_raft_service_test.rs b/d-engine-server/src/network/grpc/grpc_raft_service_test.rs similarity index 100% rename from d-engine-runtime/src/network/grpc/grpc_raft_service_test.rs rename to d-engine-server/src/network/grpc/grpc_raft_service_test.rs diff --git a/d-engine-runtime/src/network/grpc/grpc_transport.rs b/d-engine-server/src/network/grpc/grpc_transport.rs similarity index 99% rename from d-engine-runtime/src/network/grpc/grpc_transport.rs rename to d-engine-server/src/network/grpc/grpc_transport.rs index fc4502a7..e37dd256 100644 --- a/d-engine-runtime/src/network/grpc/grpc_transport.rs +++ b/d-engine-server/src/network/grpc/grpc_transport.rs @@ -22,7 +22,6 @@ use tracing::error; use tracing::info; use tracing::warn; -use crate::scoped_timer::ScopedTimer; use d_engine_core::AppendResult; use d_engine_core::BackoffPolicy; use d_engine_core::ClusterUpdateResult; @@ -38,6 +37,7 @@ use d_engine_core::TypeConfig; use d_engine_core::VoteResult; use d_engine_core::alias::MOF; use d_engine_core::grpc_task_with_timeout_and_exponential_backoff; +use d_engine_core::scoped_timer::ScopedTimer; use d_engine_proto::server::cluster::ClusterConfChangeRequest; use d_engine_proto::server::cluster::JoinResponse; use d_engine_proto::server::cluster::LeaderDiscoveryRequest; diff --git a/d-engine-runtime/src/network/grpc/grpc_transport_test.rs b/d-engine-server/src/network/grpc/grpc_transport_test.rs similarity index 99% rename from d-engine-runtime/src/network/grpc/grpc_transport_test.rs rename to d-engine-server/src/network/grpc/grpc_transport_test.rs index 573ccf00..a9b90723 100644 --- a/d-engine-runtime/src/network/grpc/grpc_transport_test.rs +++ b/d-engine-server/src/network/grpc/grpc_transport_test.rs @@ -11,7 +11,7 @@ use tonic::transport::Endpoint; use tracing_test::traced_test; use super::*; -use crate::grpc::grpc_transport::GrpcTransport; +use crate::network::grpc::grpc_transport::GrpcTransport; use d_engine_core::ConnectionType; use d_engine_core::Error; use d_engine_core::MockMembership; diff --git a/d-engine-runtime/src/network/grpc/mod.rs b/d-engine-server/src/network/grpc/mod.rs similarity index 100% rename from d-engine-runtime/src/network/grpc/mod.rs rename to d-engine-server/src/network/grpc/mod.rs diff --git a/d-engine-runtime/src/network/health_checker.rs b/d-engine-server/src/network/health_checker.rs similarity index 98% rename from d-engine-runtime/src/network/health_checker.rs rename to d-engine-server/src/network/health_checker.rs index 5fb6491e..3d07249e 100644 --- a/d-engine-runtime/src/network/health_checker.rs +++ b/d-engine-server/src/network/health_checker.rs @@ -12,7 +12,7 @@ use tonic_health::pb::health_check_response::ServingStatus; use tonic_health::pb::health_client::HealthClient; use tracing::error; -use crate::net::address_str; +use crate::utils::net::address_str; use d_engine_core::NetworkConfig; use d_engine_core::NetworkError; use d_engine_core::Result; diff --git a/d-engine-runtime/src/network/health_checker_test.rs b/d-engine-server/src/network/health_checker_test.rs similarity index 100% rename from d-engine-runtime/src/network/health_checker_test.rs rename to d-engine-server/src/network/health_checker_test.rs diff --git a/d-engine-runtime/src/network/health_monitor.rs b/d-engine-server/src/network/health_monitor.rs similarity index 100% rename from d-engine-runtime/src/network/health_monitor.rs rename to d-engine-server/src/network/health_monitor.rs diff --git a/d-engine-runtime/src/network/health_monitor_test.rs b/d-engine-server/src/network/health_monitor_test.rs similarity index 100% rename from d-engine-runtime/src/network/health_monitor_test.rs rename to d-engine-server/src/network/health_monitor_test.rs diff --git a/d-engine-runtime/src/network/mod.rs b/d-engine-server/src/network/mod.rs similarity index 100% rename from d-engine-runtime/src/network/mod.rs rename to d-engine-server/src/network/mod.rs diff --git a/d-engine-runtime/src/network/network_test.rs b/d-engine-server/src/network/network_test.rs similarity index 100% rename from d-engine-runtime/src/network/network_test.rs rename to d-engine-server/src/network/network_test.rs diff --git a/d-engine-runtime/src/node/builder.rs b/d-engine-server/src/node/builder.rs similarity index 96% rename from d-engine-runtime/src/node/builder.rs rename to d-engine-server/src/node/builder.rs index 738c0f22..5745db74 100644 --- a/d-engine-runtime/src/node/builder.rs +++ b/d-engine-server/src/node/builder.rs @@ -47,11 +47,11 @@ use tracing::error; use tracing::info; use super::RaftTypeConfig; -use crate::BufferedRaftLog; use crate::Node; -use crate::RaftMembership; -use crate::grpc; -use crate::grpc::grpc_transport::GrpcTransport; +use crate::membership::RaftMembership; +use crate::network::grpc; +use crate::network::grpc::grpc_transport::GrpcTransport; +use crate::storage::BufferedRaftLog; use d_engine_core::ClusterConfig; use d_engine_core::CommitHandler; use d_engine_core::CommitHandlerDependencies; @@ -81,14 +81,21 @@ use d_engine_core::alias::TROF; use d_engine_core::follower_state::FollowerState; use d_engine_core::learner_state::LearnerState; -pub enum NodeMode { - Joiner, - FullMember, -} - -/// Builder pattern implementation for constructing a Raft node with configurable components. -/// Provides a fluent interface to set up node configuration, storage, transport, and other -/// dependencies. +/// Builder for creating a Raft node +/// +/// Provides a fluent API for configuring and constructing a [`Node`]. +/// +/// # Example +/// +/// ```rust,ignore +/// use d_engine_server::{NodeBuilder, FileStorageEngine, FileStateMachine}; +/// +/// let node = NodeBuilder::new(None, shutdown_rx) +/// .storage_engine(Arc::new(FileStorageEngine::new(...)?)) +/// .state_machine(Arc::new(FileStateMachine::new(...).await?)) +/// .build() +/// .ready()?; +/// ``` pub struct NodeBuilder where SE: StorageEngine + Debug, @@ -410,14 +417,6 @@ where self } - pub fn set_snapshot_policy( - mut self, - snapshot_policy: SNP>, - ) -> Self { - self.snapshot_policy = Some(snapshot_policy); - self - } - /// Starts the gRPC server for cluster communication. /// /// # Panics diff --git a/d-engine-runtime/src/node/builder_test.rs b/d-engine-server/src/node/builder_test.rs similarity index 99% rename from d-engine-runtime/src/node/builder_test.rs rename to d-engine-server/src/node/builder_test.rs index f1f41435..7c6e8dcc 100644 --- a/d-engine-runtime/src/node/builder_test.rs +++ b/d-engine-server/src/node/builder_test.rs @@ -7,11 +7,11 @@ use tempfile::tempdir; use tokio::sync::watch; use tracing_test::traced_test; -use crate::BufferedRaftLog; use crate::FileStateMachine; use crate::FileStorageEngine; use crate::NodeBuilder; -use crate::RaftTypeConfig; +use crate::node::RaftTypeConfig; +use crate::storage::BufferedRaftLog; use crate::test_utils::insert_raft_log; use crate::test_utils::insert_state_machine; use crate::test_utils::mock_state_machine; diff --git a/d-engine-runtime/src/node/mod.rs b/d-engine-server/src/node/mod.rs similarity index 93% rename from d-engine-runtime/src/node/mod.rs rename to d-engine-server/src/node/mod.rs index c110f114..bc213b22 100644 --- a/d-engine-runtime/src/node/mod.rs +++ b/d-engine-server/src/node/mod.rs @@ -49,7 +49,19 @@ use d_engine_core::Result; use d_engine_core::TypeConfig; use d_engine_core::alias::MOF; -/// Raft node container +/// Raft consensus node +/// +/// Represents a single node participating in a Raft cluster. +/// Coordinates protocol execution, storage, and networking. +/// +/// Created via [`NodeBuilder`]. +/// +/// # Running the Node +/// +/// ```rust,ignore +/// let node = builder.ready()?; +/// node.run().await?; // Blocks until shutdown +/// ``` pub struct Node where T: TypeConfig, diff --git a/d-engine-runtime/src/node/node_test.rs b/d-engine-server/src/node/node_test.rs similarity index 100% rename from d-engine-runtime/src/node/node_test.rs rename to d-engine-server/src/node/node_test.rs diff --git a/d-engine-runtime/src/node/type_config/mod.rs b/d-engine-server/src/node/type_config/mod.rs similarity index 100% rename from d-engine-runtime/src/node/type_config/mod.rs rename to d-engine-server/src/node/type_config/mod.rs diff --git a/d-engine-runtime/src/node/type_config/raft_type_config.rs b/d-engine-server/src/node/type_config/raft_type_config.rs similarity index 88% rename from d-engine-runtime/src/node/type_config/raft_type_config.rs rename to d-engine-server/src/node/type_config/raft_type_config.rs index c998948f..0aa7ca70 100644 --- a/d-engine-runtime/src/node/type_config/raft_type_config.rs +++ b/d-engine-server/src/node/type_config/raft_type_config.rs @@ -1,8 +1,8 @@ use std::fmt::Debug; -use crate::BufferedRaftLog; -use crate::RaftMembership; -use crate::grpc::grpc_transport::GrpcTransport; +use crate::membership::RaftMembership; +use crate::network::grpc::grpc_transport::GrpcTransport; +use crate::storage::BufferedRaftLog; use d_engine_core::DefaultCommitHandler; use d_engine_core::DefaultPurgeExecutor; use d_engine_core::DefaultStateMachineHandler; diff --git a/d-engine-runtime/src/storage/adaptors/file/file_engine_test.rs b/d-engine-server/src/storage/adaptors/file/file_engine_test.rs similarity index 100% rename from d-engine-runtime/src/storage/adaptors/file/file_engine_test.rs rename to d-engine-server/src/storage/adaptors/file/file_engine_test.rs diff --git a/d-engine-runtime/src/storage/adaptors/file/file_state_machine.rs b/d-engine-server/src/storage/adaptors/file/file_state_machine.rs similarity index 100% rename from d-engine-runtime/src/storage/adaptors/file/file_state_machine.rs rename to d-engine-server/src/storage/adaptors/file/file_state_machine.rs diff --git a/d-engine-runtime/src/storage/adaptors/file/file_state_machine_test.rs b/d-engine-server/src/storage/adaptors/file/file_state_machine_test.rs similarity index 100% rename from d-engine-runtime/src/storage/adaptors/file/file_state_machine_test.rs rename to d-engine-server/src/storage/adaptors/file/file_state_machine_test.rs diff --git a/d-engine-runtime/src/storage/adaptors/file/file_storage_engine.rs b/d-engine-server/src/storage/adaptors/file/file_storage_engine.rs similarity index 96% rename from d-engine-runtime/src/storage/adaptors/file/file_storage_engine.rs rename to d-engine-server/src/storage/adaptors/file/file_storage_engine.rs index 93145fe5..77dea8da 100644 --- a/d-engine-runtime/src/storage/adaptors/file/file_storage_engine.rs +++ b/d-engine-server/src/storage/adaptors/file/file_storage_engine.rs @@ -51,7 +51,22 @@ pub struct FileMetaStore { data: Mutex, Vec>>, } -/// Unified file-based storage engine +/// File-based Raft log storage +/// +/// Stores log entries as individual files in a directory. +/// +/// # Usage +/// +/// ```rust,ignore +/// use d_engine_server::FileStorageEngine; +/// use std::path::PathBuf; +/// +/// let engine = FileStorageEngine::new(PathBuf::from("/tmp/log"))?; +/// ``` +/// +/// # Performance +/// +/// Suitable for development and testing. For production, use [`RocksDBStorageEngine`]. #[derive(Debug)] pub struct FileStorageEngine { log_store: Arc, @@ -223,7 +238,7 @@ impl FileLogStore { } #[cfg(any(test, feature = "test-utils"))] - pub fn reset_sync(&self) -> Result<(), Error> { + pub(crate) fn reset_sync(&self) -> Result<(), Error> { { let mut file = self.file_handle.lock().unwrap(); file.set_len(0)?; diff --git a/d-engine-runtime/src/storage/adaptors/file/file_storage_engine_test.rs b/d-engine-server/src/storage/adaptors/file/file_storage_engine_test.rs similarity index 100% rename from d-engine-runtime/src/storage/adaptors/file/file_storage_engine_test.rs rename to d-engine-server/src/storage/adaptors/file/file_storage_engine_test.rs diff --git a/d-engine-runtime/src/storage/adaptors/file/mod.rs b/d-engine-server/src/storage/adaptors/file/mod.rs similarity index 100% rename from d-engine-runtime/src/storage/adaptors/file/mod.rs rename to d-engine-server/src/storage/adaptors/file/mod.rs diff --git a/d-engine-runtime/src/storage/adaptors/mod.rs b/d-engine-server/src/storage/adaptors/mod.rs similarity index 100% rename from d-engine-runtime/src/storage/adaptors/mod.rs rename to d-engine-server/src/storage/adaptors/mod.rs diff --git a/d-engine-runtime/src/storage/adaptors/rocksdb/mod.rs b/d-engine-server/src/storage/adaptors/rocksdb/mod.rs similarity index 100% rename from d-engine-runtime/src/storage/adaptors/rocksdb/mod.rs rename to d-engine-server/src/storage/adaptors/rocksdb/mod.rs diff --git a/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs b/d-engine-server/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs similarity index 100% rename from d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs rename to d-engine-server/src/storage/adaptors/rocksdb/rocksdb_engine_test.rs diff --git a/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs b/d-engine-server/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs similarity index 100% rename from d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs rename to d-engine-server/src/storage/adaptors/rocksdb/rocksdb_state_machine.rs diff --git a/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs b/d-engine-server/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs similarity index 96% rename from d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs rename to d-engine-server/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs index cfe693c2..89086761 100644 --- a/d-engine-runtime/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs +++ b/d-engine-server/src/storage/adaptors/rocksdb/rocksdb_storage_engine.rs @@ -40,7 +40,26 @@ pub struct RocksDBMetaStore { db: Arc, } -/// Unified RocksDB-based storage engine +/// RocksDB-based Raft log storage +/// +/// High-performance storage engine using RocksDB for durability. +/// Recommended for production deployments. +/// +/// # Usage +/// +/// ```rust,ignore +/// use d_engine_server::RocksDBStorageEngine; +/// use std::path::PathBuf; +/// +/// let engine = RocksDBStorageEngine::new(PathBuf::from("/tmp/raft-db"))?; +/// ``` +/// +/// # Features +/// +/// - ACID transactions +/// - Compression support +/// - Configurable write options +/// - High write throughput #[derive(Debug)] pub struct RocksDBStorageEngine { log_store: Arc, diff --git a/d-engine-runtime/src/storage/buffered/buffered_raft_log.rs b/d-engine-server/src/storage/buffered/buffered_raft_log.rs similarity index 98% rename from d-engine-runtime/src/storage/buffered/buffered_raft_log.rs rename to d-engine-server/src/storage/buffered/buffered_raft_log.rs index 78266a81..afe57bea 100644 --- a/d-engine-runtime/src/storage/buffered/buffered_raft_log.rs +++ b/d-engine-server/src/storage/buffered/buffered_raft_log.rs @@ -38,7 +38,6 @@ use tracing::error; use tracing::trace; use tracing::warn; -use crate::scoped_timer::ScopedTimer; use d_engine_core::Error; use d_engine_core::FlushPolicy; use d_engine_core::HardState; @@ -52,19 +51,22 @@ use d_engine_core::Result; use d_engine_core::StorageEngine; use d_engine_core::TypeConfig; use d_engine_core::alias::SOF; +use d_engine_core::scoped_timer::ScopedTimer; use d_engine_proto::common::Entry; use d_engine_proto::common::LogId; -pub struct FlushWorkerPool +pub(crate) struct FlushWorkerPool where T: TypeConfig, { sender: Option>>, + #[allow(dead_code)] shutdown: Arc, - pub worker_handles: Vec>, + #[allow(dead_code)] + pub(super) worker_handles: Vec>, } -pub struct FlushTask +pub(crate) struct FlushTask where T: TypeConfig, { @@ -87,7 +89,7 @@ pub enum LogCommand { Shutdown, } -pub struct FlushState { +pub(crate) struct FlushState { pending_indexes: Vec, // Indexes pending flush } @@ -108,19 +110,19 @@ where #[allow(dead_code)] node_id: u32, - pub log_store: Arc< as StorageEngine>::LogStore>, - pub meta_store: Arc< as StorageEngine>::MetaStore>, + pub(crate) log_store: Arc< as StorageEngine>::LogStore>, + pub(crate) meta_store: Arc< as StorageEngine>::MetaStore>, pub(crate) strategy: PersistenceStrategy, pub(crate) flush_policy: FlushPolicy, // --- In-memory state --- // Pending entries - pub entries: SkipMap, + pub(crate) entries: SkipMap, // Tracks the highest index that has been persisted to disk - pub durable_index: AtomicU64, + pub(crate) durable_index: AtomicU64, // The next index to be allocated - pub next_id: AtomicU64, + pub(crate) next_id: AtomicU64, // --- In-memory index --- min_index: AtomicU64, // Smallest log index (0 if empty) @@ -134,10 +136,10 @@ where pub(crate) command_sender: mpsc::UnboundedSender, // Track flush state pub(crate) flush_state: Mutex, - pub waiters: DashMap>>, + pub(crate) waiters: DashMap>>, // --- Flush worker pool --- - pub flush_workers: FlushWorkerPool, + pub(crate) flush_workers: FlushWorkerPool, } #[async_trait] @@ -532,7 +534,7 @@ impl BufferedRaftLog where T: TypeConfig, { - pub fn new( + pub(crate) fn new( node_id: u32, persistence_config: PersistenceConfig, storage: Arc>, @@ -1155,7 +1157,8 @@ where } // Cleanup method for graceful shutdown - pub async fn shutdown(&mut self) { + #[allow(dead_code)] + pub(crate) async fn shutdown(&mut self) { // Signal shutdown to all workers self.flush_workers.shutdown.store(true, Ordering::Relaxed); @@ -1184,7 +1187,8 @@ where } #[cfg(any(test, feature = "test-utils"))] - pub fn len(&self) -> usize { + #[allow(dead_code)] + pub(crate) fn len(&self) -> usize { self.entries.len() } diff --git a/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs b/d-engine-server/src/storage/buffered/buffered_raft_log_test.rs similarity index 99% rename from d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs rename to d-engine-server/src/storage/buffered/buffered_raft_log_test.rs index 40138477..1da4ff21 100644 --- a/d-engine-runtime/src/storage/buffered/buffered_raft_log_test.rs +++ b/d-engine-server/src/storage/buffered/buffered_raft_log_test.rs @@ -14,9 +14,9 @@ use tracing::debug; use tracing_test::traced_test; use super::*; -use crate::BufferedRaftLog; use crate::FileStorageEngine; -use crate::RaftTypeConfig; +use crate::node::RaftTypeConfig; +use crate::storage::BufferedRaftLog; use crate::test_utils::{self}; use d_engine_core::FlushPolicy; use d_engine_core::LogStore; diff --git a/d-engine-runtime/src/storage/buffered/mod.rs b/d-engine-server/src/storage/buffered/mod.rs similarity index 100% rename from d-engine-runtime/src/storage/buffered/mod.rs rename to d-engine-server/src/storage/buffered/mod.rs diff --git a/d-engine-runtime/src/storage/mod.rs b/d-engine-server/src/storage/mod.rs similarity index 100% rename from d-engine-runtime/src/storage/mod.rs rename to d-engine-server/src/storage/mod.rs diff --git a/d-engine-runtime/src/test_utils/integration/mod.rs b/d-engine-server/src/test_utils/integration/mod.rs similarity index 63% rename from d-engine-runtime/src/test_utils/integration/mod.rs rename to d-engine-server/src/test_utils/integration/mod.rs index ed443ad8..22cea1fd 100644 --- a/d-engine-runtime/src/test_utils/integration/mod.rs +++ b/d-engine-server/src/test_utils/integration/mod.rs @@ -37,13 +37,13 @@ //! - Cluster formation and interaction tests //! - Failure scenario testing with real component interactions -use crate::BufferedRaftLog; +use super::mock::mock_state_machine; use crate::FileStateMachine; use crate::FileStorageEngine; -use crate::RaftMembership; -use crate::RaftTypeConfig; -use crate::grpc::grpc_transport::GrpcTransport; -use crate::test_utils::mock_state_machine; +use crate::membership::RaftMembership; +use crate::network::grpc::grpc_transport::GrpcTransport; +use crate::node::RaftTypeConfig; +use crate::storage::BufferedRaftLog; use d_engine_core::DefaultStateMachineHandler; use d_engine_core::ElectionHandler; use d_engine_core::FlushPolicy; @@ -72,40 +72,92 @@ use std::ops::RangeInclusive; use std::path::PathBuf; use std::sync::Arc; use tokio::sync::watch; +use tracing::debug; -#[allow(dead_code)] +/// Complete testing environment for Raft consensus algorithm integration tests. +/// +/// This struct encapsulates all necessary components for testing Raft protocol +/// implementation with real storage, network, and handler components. It provides +/// a self-contained testing context that can be initialized via [`setup_raft_components`]. +/// +/// # Fields +/// +/// * `id` - The node ID in the cluster +/// * `raft_log` - The persistent Raft log storage with buffering +/// * `state_machine` - The state machine for applying committed entries +/// * `transport` - The gRPC transport layer for inter-node communication +/// * `membership` - The cluster membership management +/// * `election_handler` - Handles election protocol logic +/// * `replication_handler` - Handles log replication between nodes +/// * `state_machine_handler` - Handles state machine commit operations +/// * `node_config` - Raft node configuration settings +/// * `arc_node_config` - Shared reference to node configuration pub struct TestContext where T: TypeConfig, { + /// The node ID in the cluster pub id: u32, // Storages + /// The persistent Raft log storage with buffering pub raft_log: Arc>, + /// The state machine for applying committed entries pub state_machine: Arc>, // Network + /// The gRPC transport layer for inter-node communication pub transport: Arc>, // Cluster Membership + /// The cluster membership management pub membership: Arc>, // Handlers + /// Handles election protocol logic pub election_handler: ElectionHandler, + /// Handles log replication between nodes pub replication_handler: ReplicationHandler, + /// Handles state machine commit operations pub state_machine_handler: DefaultStateMachineHandler, + /// Raft node configuration settings pub node_config: RaftNodeConfig, + /// Shared reference to node configuration pub arc_node_config: Arc, } -// If param `restart` is true, we will not reset dbs +/// Initializes a complete Raft testing environment with real components. +/// +/// This function sets up a fully functional test context containing real storage, +/// network, and handler components for integration testing. It configures: +/// - Fresh database instances with real file-based storage +/// - gRPC transport for network communication +/// - Cluster membership with configurable peer nodes +/// - All necessary Raft handlers (election, replication, state machine) +/// +/// # Arguments +/// +/// * `db_path` - The file system path for the database storage +/// * `peers_meta_option` - Optional cluster peer configuration. If `None`, defaults +/// to a 3-node cluster with nodes 1, 2, and 3 on ports 8080-8082 +/// * `restart` - If `true`, skips database reset and reuses existing state. +/// If `false`, performs a fresh database initialization +/// +/// # Returns +/// +/// A fully initialized [`TestContext`] ready for integration testing with +/// [`FileStorageEngine`] and [`MockStateMachine`]. +/// +/// # Panics +/// +/// Panics if storage engine creation or initialization fails. pub fn setup_raft_components( db_path: &str, peers_meta_option: Option>, restart: bool, ) -> TestContext> { - println!("Test setup_raft_components ..."); + debug!("Test setup_raft_components ..."); unsafe { std::env::remove_var("CONFIG_PATH"); @@ -206,7 +258,22 @@ pub fn setup_raft_components( } } -pub async fn insert_raft_log( +/// Inserts entries into the Raft log with specified IDs and term. +/// +/// Creates insert command entries and appends them to the Raft log storage. +/// Each ID becomes a separate log entry with the given term. +/// +/// # Arguments +/// +/// * `raft_log` - Reference to the Raft log storage +/// * `ids` - Vector of IDs to insert as separate log entries +/// * `term` - The term for all inserted entries +/// +/// # Panics +/// +/// Panics if the batch insert operation fails. +#[allow(dead_code)] +pub(crate) async fn insert_raft_log( raft_log: &Arc>>, ids: Vec, term: u64, @@ -225,7 +292,23 @@ pub async fn insert_raft_log( } } -pub async fn insert_state_machine( +/// Applies entries to the state machine with specified IDs and term. +/// +/// Creates insert command entries and applies them directly to the state machine, +/// simulating the application of committed log entries. Each ID becomes a separate +/// entry with the given term. +/// +/// # Arguments +/// +/// * `state_machine` - Reference to the state machine +/// * `ids` - Vector of IDs to insert as separate entries +/// * `term` - The term for all inserted entries +/// +/// # Panics +/// +/// Panics if the chunk application operation fails. +#[allow(dead_code)] +pub(crate) async fn insert_state_machine( state_machine: &SMOF>, ids: Vec, term: u64, @@ -244,8 +327,26 @@ pub async fn insert_state_machine( } } -///Dependes on external id to specify the local log entry index. -/// If duplicated ids are specified, then the only one entry will be inserted. +/// Simulates inserting commands into the Raft log with automatic log entry allocation. +/// +/// Creates insert command entries and appends them to the Raft log storage, then +/// flushes the log to ensure durability. The log entry indices are automatically +/// allocated by the Raft log, not specified externally. +/// +/// # Arguments +/// +/// * `raft_log` - Reference to the Raft log storage +/// * `ids` - Vector of IDs to insert as separate commands +/// * `term` - The term for all inserted entries +/// +/// # Panics +/// +/// Panics if the batch insert or flush operation fails. +/// +/// # Note +/// +/// If duplicate IDs are specified, only one entry per ID will be inserted due to +/// the internal command generation logic. pub async fn simulate_insert_command( raft_log: &Arc>>, ids: Vec, @@ -264,6 +365,21 @@ pub async fn simulate_insert_command( raft_log.flush().await.unwrap(); } +/// Simulates deleting entries from the log for a range of IDs. +/// +/// Creates delete command entries for each ID in the specified range and appends +/// them to the Raft log storage. Each ID in the range becomes a separate delete +/// command entry with the given term. +/// +/// # Arguments +/// +/// * `raft_log` - Reference to the Raft log storage +/// * `id_range` - An inclusive range of IDs to delete +/// * `term` - The term for all delete entries +/// +/// # Panics +/// +/// Panics if the batch insert operation fails. #[allow(dead_code)] pub async fn simulate_delete_command( raft_log: &Arc>>, diff --git a/d-engine-runtime/src/test_utils/mock/mock_node_builder.rs b/d-engine-server/src/test_utils/mock/mock_node_builder.rs similarity index 75% rename from d-engine-runtime/src/test_utils/mock/mock_node_builder.rs rename to d-engine-server/src/test_utils/mock/mock_node_builder.rs index 9cfb9fe8..c1c49318 100644 --- a/d-engine-runtime/src/test_utils/mock/mock_node_builder.rs +++ b/d-engine-server/src/test_utils/mock/mock_node_builder.rs @@ -1,3 +1,5 @@ +//! Mock node builder for fluent configuration of test Raft instances + use std::path::Path; use std::sync::Arc; use std::sync::atomic::AtomicBool; @@ -11,7 +13,7 @@ use tracing::trace; use super::MockTypeConfig; use crate::Node; -use crate::grpc; +use crate::network::grpc; use d_engine_core::ElectionConfig; use d_engine_core::MockElectionCore; use d_engine_core::MockMembership; @@ -37,29 +39,101 @@ use d_engine_core::follower_state::FollowerState; use d_engine_proto::common::LogId; use d_engine_proto::server::cluster::ClusterMembership; +/// Builder for constructing mock Raft components with customizable defaults +/// +/// This builder provides a fluent API for creating test fixtures with +/// fine-grained control over individual Raft components. +/// +/// # Example: Basic Usage +/// +/// ```rust,ignore +/// use d_engine_server::test_utils::MockBuilder; +/// use tokio::sync::watch; +/// +/// let (tx, rx) = watch::channel(()); +/// let raft = MockBuilder::new(rx) +/// .id(1) +/// .build_raft(); +/// ``` +/// +/// # Example: Custom Configuration +/// +/// ```rust,ignore +/// use d_engine_server::test_utils::MockBuilder; +/// use tokio::sync::watch; +/// +/// let (tx, rx) = watch::channel(()); +/// let node = MockBuilder::new(rx) +/// .id(2) +/// .with_db_path("/tmp/test") +/// .turn_on_election(false) +/// .build_node(); +/// ``` +/// +/// # Builder Methods +/// +/// - `id()` - Set node ID +/// - `with_role()` - Set custom Raft role +/// - `with_raft_log()` - Use custom log implementation +/// - `with_state_machine()` - Use custom state machine +/// - `with_transport()` - Use custom transport +/// - `with_membership()` - Use custom membership handler +/// - `turn_on_election()` - Enable/disable election +/// +/// # Building Variants +/// +/// - `build_context()` - Build RaftContext only +/// - `build_raft()` - Build Raft instance (most common) +/// - `build_node()` - Build Node wrapper +/// - `build_node_with_rpc_server()` - Build Node with gRPC server pub struct MockBuilder { + /// Optional node ID (defaults to 1) pub id: Option, + /// Optional initial Raft role pub role: Option>, + /// Optional Raft log implementation pub raft_log: Option, + /// Optional state machine implementation pub state_machine: Option>, + /// Optional network transport pub transport: Option>, + /// Optional membership handler pub membership: Option>>, + /// Optional purge executor pub purge_executor: Option, + /// Optional election handler pub election_handler: Option>, + /// Optional replication handler pub replication_handler: Option>, + /// Optional state machine handler pub state_machine_handler: Option>>, + /// Optional node configuration pub node_config: Option, + /// Optional election toggle (defaults to true) pub turn_on_election: Option, + /// Shutdown signal for the Raft instance shutdown_signal: watch::Receiver<()>, + /// Internal event channel sender pub(crate) event_tx: Option>, + /// Internal event channel receiver pub(crate) event_rx: Option>, + /// Internal role change channel sender pub(crate) role_tx: Option>, + /// Internal role change channel receiver pub(crate) role_rx: Option>, } impl MockBuilder { + /// Create a new MockBuilder with default components + /// + /// All components are initially `None`, and will use mock defaults + /// when building unless explicitly set. + /// + /// # Arguments + /// + /// * `shutdown_signal` - Watch receiver for graceful shutdown pub fn new(shutdown_signal: watch::Receiver<()>) -> Self { Self { id: None, @@ -83,6 +157,9 @@ impl MockBuilder { } } + /// Build a RaftContext with current configuration + /// + /// Returns the low-level Raft context for testing protocol logic. pub fn build_context(self) -> RaftContext { let ( raft_log, @@ -122,6 +199,10 @@ impl MockBuilder { mock_raft_context_internal(1, storage, transport, membership, handlers, node_config) } + /// Build a Raft instance with current configuration + /// + /// This is the most commonly used build method. Returns a fully + /// initialized Raft instance ready for testing. pub fn build_raft(self) -> Raft { let (role_tx, role_rx) = mpsc::unbounded_channel(); let (event_tx, event_rx) = mpsc::channel(10); @@ -213,6 +294,9 @@ impl MockBuilder { ) } + /// Build a Node instance (without RPC server) + /// + /// Wraps the Raft instance in a Node container for integration testing. pub fn build_node(self) -> Node { let raft = self.build_raft(); let event_tx = raft.event_tx.clone(); @@ -228,6 +312,10 @@ impl MockBuilder { } } + /// Build a Node instance with integrated gRPC server + /// + /// This variant starts the RPC server in a background task, + /// useful for end-to-end testing with network communication. pub fn build_node_with_rpc_server(self) -> Arc> { let shutdown = self.shutdown_signal.clone(); let node_config_option = self.node_config.clone(); @@ -264,6 +352,8 @@ impl MockBuilder { node } + + /// Set the node ID (defaults to 1) pub fn id( mut self, id: u32, @@ -271,6 +361,8 @@ impl MockBuilder { self.id = Some(id); self } + + /// Set a custom initial Raft role pub fn with_role( mut self, role: RaftRole, @@ -278,6 +370,8 @@ impl MockBuilder { self.role = Some(role); self } + + /// Set a custom Raft log implementation pub fn with_raft_log( mut self, raft_log: MockRaftLog, @@ -285,6 +379,8 @@ impl MockBuilder { self.raft_log = Some(raft_log); self } + + /// Set a custom state machine implementation pub fn with_state_machine( mut self, sm: MockStateMachine, @@ -293,6 +389,7 @@ impl MockBuilder { self } + /// Set a custom network transport pub fn with_transport( mut self, transport: MockTransport, @@ -301,6 +398,7 @@ impl MockBuilder { self } + /// Set a custom membership handler pub fn with_membership( mut self, membership: MockMembership, @@ -309,6 +407,7 @@ impl MockBuilder { self } + /// Set a custom election handler pub fn with_election_handler( mut self, election_handler: MockElectionCore, @@ -317,6 +416,7 @@ impl MockBuilder { self } + /// Set a custom replication handler pub fn with_replication_handler( mut self, replication_handler: MockReplicationCore, @@ -325,6 +425,7 @@ impl MockBuilder { self } + /// Set a custom state machine handler pub fn with_state_machine_handler( mut self, state_machine_handler: MockStateMachineHandler, @@ -333,6 +434,7 @@ impl MockBuilder { self } + /// Set custom node configuration pub fn with_node_config( mut self, node_config: RaftNodeConfig, @@ -341,6 +443,9 @@ impl MockBuilder { self } + /// Set the database root directory path + /// + /// Automatically updates the node configuration with the provided path. pub fn with_db_path( mut self, db_root_dir: impl AsRef, @@ -352,6 +457,9 @@ impl MockBuilder { self } + /// Enable or disable election handling (defaults to enabled) + /// + /// Set to `false` to prevent automatic elections during testing. pub fn turn_on_election( mut self, is_on: bool, @@ -361,7 +469,10 @@ impl MockBuilder { } } -pub fn mock_raft_log() -> MockRaftLog { +// ==================== Mock Factory Functions ==================== + +/// Create a mock Raft log with sensible test defaults +pub(crate) fn mock_raft_log() -> MockRaftLog { let mut raft_log = MockRaftLog::new(); raft_log.expect_last_entry_id().returning(|| 0); raft_log.expect_last_log_id().returning(|| None); @@ -371,11 +482,13 @@ pub fn mock_raft_log() -> MockRaftLog { raft_log } -pub fn mock_transport() -> MockTransport { +/// Create a mock network transport +pub(crate) fn mock_transport() -> MockTransport { MockTransport::new() } -pub fn mock_election_core() -> MockElectionCore { +/// Create a mock election handler +pub(crate) fn mock_election_core() -> MockElectionCore { let mut election_handler = MockElectionCore::new(); election_handler .expect_broadcast_vote_requests() @@ -383,11 +496,13 @@ pub fn mock_election_core() -> MockElectionCore { election_handler } -pub fn mock_replication_handler() -> MockReplicationCore { +/// Create a mock replication handler +pub(crate) fn mock_replication_handler() -> MockReplicationCore { MockReplicationCore::new() } -pub fn mock_state_machine() -> MockStateMachine { +/// Create a mock state machine with all methods stubbed +pub(crate) fn mock_state_machine() -> MockStateMachine { let mut mock = MockStateMachine::new(); mock.expect_start().returning(|| Ok(())); @@ -417,19 +532,23 @@ pub fn mock_state_machine() -> MockStateMachine { mock } -pub fn mock_state_machine_handler() -> MockStateMachineHandler { +/// Create a mock state machine handler +pub(crate) fn mock_state_machine_handler() -> MockStateMachineHandler { let mut state_machine_handler = MockStateMachineHandler::new(); state_machine_handler.expect_update_pending().returning(|_| {}); state_machine_handler.expect_read_from_state_machine().returning(|_| None); state_machine_handler } -pub fn mock_purge_exewcutor() -> MockPurgeExecutor { +/// Create a mock purge executor +pub(crate) fn mock_purge_exewcutor() -> MockPurgeExecutor { let mut purge_exewcutor = MockPurgeExecutor::new(); purge_exewcutor.expect_execute_purge().returning(|_| Ok(())); purge_exewcutor } -pub fn mock_membership() -> MockMembership { + +/// Create a mock membership handler +pub(crate) fn mock_membership() -> MockMembership { let mut membership = MockMembership::new(); membership.expect_can_rejoin().returning(|_, _| Ok(())); membership.expect_pre_warm_connections().returning(|| Ok(())); @@ -451,7 +570,8 @@ pub fn mock_membership() -> MockMembership { membership } -fn mock_raft_context_internal( +/// Create a RaftContext with mock components +pub(crate) fn mock_raft_context_internal( id: u32, storage: RaftStorageHandles, transport: Arc>, diff --git a/d-engine-runtime/src/test_utils/mock/mock_rpc_services.rs b/d-engine-server/src/test_utils/mock/mock_rpc_services.rs similarity index 100% rename from d-engine-runtime/src/test_utils/mock/mock_rpc_services.rs rename to d-engine-server/src/test_utils/mock/mock_rpc_services.rs diff --git a/d-engine-server/src/test_utils/mock/mod.rs b/d-engine-server/src/test_utils/mock/mod.rs new file mode 100644 index 00000000..0a443af6 --- /dev/null +++ b/d-engine-server/src/test_utils/mock/mod.rs @@ -0,0 +1,87 @@ +//! Mock implementations for Raft components +//! +//! Provides convenient builders and factory functions for creating +//! mock Raft instances with sensible defaults. + +mod mock_node_builder; + +pub use mock_node_builder::*; + +use crate::Node; +use d_engine_core::node_config; +use d_engine_core::{MockTypeConfig, Raft}; +use d_engine_proto::server::cluster::NodeMeta; +use tokio::sync::watch; + +/// Create a mock Raft node for testing +/// +/// Constructs a Node instance with mock components and optional peer metadata. +/// +/// # Arguments +/// +/// * `db_path` - Path for database configuration +/// * `shutdown_signal` - Watch receiver for graceful shutdown +/// * `peers_meta_option` - Optional list of peer node metadata +/// +/// # Example +/// +/// ```rust,ignore +/// use d_engine_server::test_utils::mock_node; +/// use tokio::sync::watch; +/// +/// let (tx, rx) = watch::channel(()); +/// let node = mock_node("/tmp/test_db", rx, None); +/// ``` +pub fn mock_node( + db_path: &str, + shutdown_signal: watch::Receiver<()>, + peers_meta_option: Option>, +) -> Node { + let mut node_config = node_config(db_path); + if let Some(peers_meta) = peers_meta_option { + node_config.cluster.initial_cluster = peers_meta; + } + // Initializing Shutdown Signal + // let (graceful_tx, graceful_rx) = watch::channel(()); + MockBuilder::new(shutdown_signal).with_node_config(node_config).build_node() +} + +/// Create a mock Raft instance for testing +/// +/// Constructs a Raft instance with all components mocked and optimized +/// for testing (e.g., batch threshold set to 0 for immediate replication). +/// +/// # Arguments +/// +/// * `db_path` - Path for database configuration +/// * `shutdown_signal` - Watch receiver for graceful shutdown +/// * `peers_meta_option` - Optional list of peer node metadata +/// +/// # Example +/// +/// ```rust,ignore +/// use d_engine_server::test_utils::mock_raft; +/// use tokio::sync::watch; +/// +/// let (tx, rx) = watch::channel(()); +/// let raft = mock_raft("/tmp/test_db", rx, None); +/// ``` +/// +/// # Note +/// +/// This sets `rpc_append_entries_in_batch_threshold=0` to ensure +/// replication happens immediately (useful for testing). +pub fn mock_raft( + db_path: &str, + shutdown_signal: watch::Receiver<()>, + peers_meta_option: Option>, +) -> Raft { + let mut node_config = node_config(db_path); + // Set batch_threshold=0, means the replication will be triggered immediatelly. + node_config.raft.replication.rpc_append_entries_in_batch_threshold = 0; + if let Some(peers_meta) = peers_meta_option { + node_config.cluster.initial_cluster = peers_meta; + } + + MockBuilder::new(shutdown_signal).with_node_config(node_config).build_raft() +} diff --git a/d-engine-server/src/test_utils/mod.rs b/d-engine-server/src/test_utils/mod.rs new file mode 100644 index 00000000..c60bc263 --- /dev/null +++ b/d-engine-server/src/test_utils/mod.rs @@ -0,0 +1,28 @@ +//! Test utilities and mock implementations +//! +//! This module provides builders and fixtures for testing d-engine-server. +//! +//! # Features +//! +//! - **Mock Builder**: Easily construct mock Raft instances with custom components +//! - **Integration Helpers**: Utilities for integration testing +//! +//! # Example +//! +//! ```rust,ignore +//! use d_engine_server::test_utils::MockBuilder; +//! use tokio::sync::watch; +//! +//! let (tx, rx) = watch::channel(()); +//! let mock_raft = MockBuilder::new(rx).build_raft(); +//! ``` +//! +//! # Note +//! +//! This module is only available with the `test-utils` feature or during tests. + +mod integration; +mod mock; + +pub use integration::*; +pub use mock::*; diff --git a/d-engine-runtime/src/utils/async_task.rs b/d-engine-server/src/utils/async_task.rs similarity index 100% rename from d-engine-runtime/src/utils/async_task.rs rename to d-engine-server/src/utils/async_task.rs diff --git a/d-engine-runtime/src/utils/async_task_test.rs b/d-engine-server/src/utils/async_task_test.rs similarity index 97% rename from d-engine-runtime/src/utils/async_task_test.rs rename to d-engine-server/src/utils/async_task_test.rs index eda493f4..fdad5cdc 100644 --- a/d-engine-runtime/src/utils/async_task_test.rs +++ b/d-engine-server/src/utils/async_task_test.rs @@ -1,5 +1,5 @@ -use crate::async_task::spawn_task; -use crate::async_task::task_with_timeout_and_exponential_backoff; +use super::async_task::*; + use d_engine_core::BackoffPolicy; use d_engine_core::Error; use futures::future::join_all; diff --git a/d-engine-runtime/src/utils/cluster.rs b/d-engine-server/src/utils/cluster.rs similarity index 100% rename from d-engine-runtime/src/utils/cluster.rs rename to d-engine-server/src/utils/cluster.rs diff --git a/d-engine-runtime/src/utils/mod.rs b/d-engine-server/src/utils/mod.rs similarity index 82% rename from d-engine-runtime/src/utils/mod.rs rename to d-engine-server/src/utils/mod.rs index 77bae00c..b87233e6 100644 --- a/d-engine-runtime/src/utils/mod.rs +++ b/d-engine-server/src/utils/mod.rs @@ -7,9 +7,6 @@ pub mod cluster; #[allow(dead_code)] pub mod net; -#[allow(dead_code)] -pub mod scoped_timer; - #[cfg(test)] mod utils_test; diff --git a/d-engine-runtime/src/utils/net.rs b/d-engine-server/src/utils/net.rs similarity index 100% rename from d-engine-runtime/src/utils/net.rs rename to d-engine-server/src/utils/net.rs diff --git a/d-engine-runtime/src/utils/net_test.rs b/d-engine-server/src/utils/net_test.rs similarity index 94% rename from d-engine-runtime/src/utils/net_test.rs rename to d-engine-server/src/utils/net_test.rs index ef207598..332f1a02 100644 --- a/d-engine-runtime/src/utils/net_test.rs +++ b/d-engine-server/src/utils/net_test.rs @@ -1,4 +1,4 @@ -use crate::net::address_str; +use super::net::*; #[test] fn test_address_str() { diff --git a/d-engine-runtime/src/utils/scoped_timer.rs b/d-engine-server/src/utils/scoped_timer.rs similarity index 100% rename from d-engine-runtime/src/utils/scoped_timer.rs rename to d-engine-server/src/utils/scoped_timer.rs diff --git a/d-engine-runtime/src/utils/utils_test.rs b/d-engine-server/src/utils/utils_test.rs similarity index 97% rename from d-engine-runtime/src/utils/utils_test.rs rename to d-engine-server/src/utils/utils_test.rs index 9118fe61..452eb3dd 100644 --- a/d-engine-runtime/src/utils/utils_test.rs +++ b/d-engine-server/src/utils/utils_test.rs @@ -5,8 +5,8 @@ use dashmap::DashSet; use tokio::time::sleep; use tracing_test::traced_test; +use super::async_task::*; use super::cluster::find_nearest_lower_number; -use crate::async_task::task_with_timeout_and_exponential_backoff; use crate::utils::cluster::is_majority; use d_engine_core::BackoffPolicy; use d_engine_core::NetworkError; diff --git a/d-engine-runtime/tests/components/buffered_raft_log_test.rs b/d-engine-server/tests/components/buffered_raft_log_test.rs similarity index 99% rename from d-engine-runtime/tests/components/buffered_raft_log_test.rs rename to d-engine-server/tests/components/buffered_raft_log_test.rs index 69ac1c4e..24800e1b 100644 --- a/d-engine-runtime/tests/components/buffered_raft_log_test.rs +++ b/d-engine-server/tests/components/buffered_raft_log_test.rs @@ -29,10 +29,10 @@ use d_engine_core::test_utils::generate_insert_commands; use d_engine_proto::common::Entry; use d_engine_proto::common::EntryPayload; use d_engine_proto::common::LogId; -use d_engine_runtime::BufferedRaftLog; -use d_engine_runtime::FileStorageEngine; -use d_engine_runtime::RaftTypeConfig; -use d_engine_runtime::test_utils; +use d_engine_server::BufferedRaftLog; +use d_engine_server::FileStorageEngine; +use d_engine_server::RaftTypeConfig; +use d_engine_server::test_utils; // Test utilities struct TestContext { @@ -1606,7 +1606,7 @@ mod id_allocation_tests { use std::sync::atomic::Ordering; use super::*; - use d_engine_runtime::FileStorageEngine; + use d_engine_server::FileStorageEngine; // In-memory test setup fn setup_memory() -> Arc>> { @@ -2376,7 +2376,7 @@ mod common_tests { mod filter_out_conflicts_and_append_performance_tests { use super::*; - use d_engine_runtime::FileStorageEngine; + use d_engine_server::FileStorageEngine; #[tokio::test] async fn test_filter_out_conflicts_performance_consistent_across_flush_intervals_fresh_cluster() @@ -2813,7 +2813,7 @@ mod performance_tests { } mod batch_processor_tests { - use d_engine_runtime::LogCommand; + use d_engine_server::LogCommand; use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; @@ -2955,7 +2955,7 @@ mod save_load_hard_state_tests { use d_engine_core::MetaStore; use d_engine_core::StorageEngine; use d_engine_proto::server::election::VotedFor; - use d_engine_runtime::FileStorageEngine; + use d_engine_server::FileStorageEngine; /// Test that hard state operations use the meta tree and not the log tree #[tokio::test] @@ -3211,7 +3211,7 @@ async fn test_last_entry_id_performance() { #[cfg(test)] mod remove_range_tests { use super::*; - use d_engine_runtime::test_utils; + use d_engine_server::test_utils; #[tokio::test] async fn test_remove_middle_range() { diff --git a/d-engine-runtime/tests/components/election/election_handler_test.rs b/d-engine-server/tests/components/election/election_handler_test.rs similarity index 99% rename from d-engine-runtime/tests/components/election/election_handler_test.rs rename to d-engine-server/tests/components/election/election_handler_test.rs index 20f11936..342550c7 100644 --- a/d-engine-runtime/tests/components/election/election_handler_test.rs +++ b/d-engine-server/tests/components/election/election_handler_test.rs @@ -1,4 +1,4 @@ -use d_engine_runtime::test_utils::*; +use d_engine_server::test_utils::*; use std::sync::Arc; use tokio::sync::watch; diff --git a/d-engine-runtime/tests/components/election/mod.rs b/d-engine-server/tests/components/election/mod.rs similarity index 100% rename from d-engine-runtime/tests/components/election/mod.rs rename to d-engine-server/tests/components/election/mod.rs diff --git a/d-engine-runtime/tests/components/mod.rs b/d-engine-server/tests/components/mod.rs similarity index 100% rename from d-engine-runtime/tests/components/mod.rs rename to d-engine-server/tests/components/mod.rs diff --git a/d-engine-runtime/tests/components/raft_role/candidate_state_test.rs b/d-engine-server/tests/components/raft_role/candidate_state_test.rs similarity index 99% rename from d-engine-runtime/tests/components/raft_role/candidate_state_test.rs rename to d-engine-server/tests/components/raft_role/candidate_state_test.rs index ec6ed885..c3773a91 100644 --- a/d-engine-runtime/tests/components/raft_role/candidate_state_test.rs +++ b/d-engine-server/tests/components/raft_role/candidate_state_test.rs @@ -44,7 +44,7 @@ use d_engine_proto::server::election::VotedFor; use d_engine_proto::server::replication::AppendEntriesRequest; use d_engine_proto::server::replication::AppendEntriesResponse; use d_engine_proto::server::storage::PurgeLogRequest; -use d_engine_runtime::test_utils::*; +use d_engine_server::test_utils::*; /// # Case 1: Can vote myself #[tokio::test] @@ -801,7 +801,7 @@ mod handle_client_read_request { use d_engine_core::config::ReadConsistencyPolicy as ServerPolicy; use d_engine_core::convert::safe_kv_bytes; use d_engine_proto::client::ReadConsistencyPolicy as ClientPolicy; - use d_engine_runtime::test_utils::MockBuilder; + use d_engine_server::test_utils::MockBuilder; /// # Case 6.1: test ClientReadRequest with linear request #[tokio::test] diff --git a/d-engine-runtime/tests/components/raft_role/follower_state_test.rs b/d-engine-server/tests/components/raft_role/follower_state_test.rs similarity index 99% rename from d-engine-runtime/tests/components/raft_role/follower_state_test.rs rename to d-engine-server/tests/components/raft_role/follower_state_test.rs index 5ff99401..e2aab384 100644 --- a/d-engine-runtime/tests/components/raft_role/follower_state_test.rs +++ b/d-engine-server/tests/components/raft_role/follower_state_test.rs @@ -1,6 +1,5 @@ -use d_engine_runtime::test_utils::*; - use bytes::Bytes; +use d_engine_server::test_utils::setup_raft_components; use std::sync::Arc; use tempfile::tempdir; use tokio::sync::mpsc; diff --git a/d-engine-runtime/tests/components/raft_role/leader_state_test.rs b/d-engine-server/tests/components/raft_role/leader_state_test.rs similarity index 99% rename from d-engine-runtime/tests/components/raft_role/leader_state_test.rs rename to d-engine-server/tests/components/raft_role/leader_state_test.rs index 95d1ff5a..f43782b1 100644 --- a/d-engine-runtime/tests/components/raft_role/leader_state_test.rs +++ b/d-engine-server/tests/components/raft_role/leader_state_test.rs @@ -1,4 +1,4 @@ -use d_engine_runtime::test_utils::*; +use d_engine_server::test_utils::*; use bytes::Bytes; use futures::StreamExt; diff --git a/d-engine-runtime/tests/components/raft_role/learner_state_test.rs b/d-engine-server/tests/components/raft_role/learner_state_test.rs similarity index 100% rename from d-engine-runtime/tests/components/raft_role/learner_state_test.rs rename to d-engine-server/tests/components/raft_role/learner_state_test.rs diff --git a/d-engine-runtime/tests/components/raft_role/mod.rs b/d-engine-server/tests/components/raft_role/mod.rs similarity index 100% rename from d-engine-runtime/tests/components/raft_role/mod.rs rename to d-engine-server/tests/components/raft_role/mod.rs diff --git a/d-engine-runtime/tests/components/raft_test.rs b/d-engine-server/tests/components/raft_test.rs similarity index 99% rename from d-engine-runtime/tests/components/raft_test.rs rename to d-engine-server/tests/components/raft_test.rs index 90358543..aeca34f9 100644 --- a/d-engine-runtime/tests/components/raft_test.rs +++ b/d-engine-server/tests/components/raft_test.rs @@ -39,7 +39,7 @@ use d_engine_proto::common::NodeStatus; use d_engine_proto::server::cluster::MetadataRequest; use d_engine_proto::server::cluster::NodeMeta; use d_engine_proto::server::election::VoteResponse; -use d_engine_runtime::mock_raft; +use d_engine_server::test_utils::mock_raft; /// # Case 1: Tick has higher priority than role event #[tokio::test] diff --git a/d-engine-runtime/tests/components/replication/mod.rs b/d-engine-server/tests/components/replication/mod.rs similarity index 100% rename from d-engine-runtime/tests/components/replication/mod.rs rename to d-engine-server/tests/components/replication/mod.rs diff --git a/d-engine-runtime/tests/components/replication/replication_handler_test.rs b/d-engine-server/tests/components/replication/replication_handler_test.rs similarity index 99% rename from d-engine-runtime/tests/components/replication/replication_handler_test.rs rename to d-engine-server/tests/components/replication/replication_handler_test.rs index 0861f3d7..28bfe0e5 100644 --- a/d-engine-runtime/tests/components/replication/replication_handler_test.rs +++ b/d-engine-server/tests/components/replication/replication_handler_test.rs @@ -49,10 +49,10 @@ use d_engine_proto::server::replication::AppendEntriesResponse; use d_engine_proto::server::replication::ConflictResult; use d_engine_proto::server::replication::SuccessResult; use d_engine_proto::server::replication::append_entries_response; -use d_engine_runtime::FileStorageEngine; -use d_engine_runtime::RaftTypeConfig; -use d_engine_runtime::test_utils::setup_raft_components; -use d_engine_runtime::test_utils::simulate_insert_command; +use d_engine_server::FileStorageEngine; +use d_engine_server::node::RaftTypeConfig; +use d_engine_server::test_utils::setup_raft_components; +use d_engine_server::test_utils::simulate_insert_command; /// # Case 1: The peer3's next_index is equal to /// the end of the leader's old log, @@ -866,7 +866,7 @@ mod handle_raft_request_in_batch_test { use super::*; use d_engine_core::convert::safe_kv_bytes; use d_engine_core::test_utils::node_config; - use d_engine_runtime::test_utils::MockBuilder; + use d_engine_server::test_utils::MockBuilder; /// # Case 1: No peers found /// ## Validation Criteria diff --git a/d-engine-runtime/tests/integration_test.rs b/d-engine-server/tests/integration_test.rs similarity index 100% rename from d-engine-runtime/tests/integration_test.rs rename to d-engine-server/tests/integration_test.rs diff --git a/examples/rocksdb-cluster/Cargo.lock b/examples/rocksdb-cluster/Cargo.lock index c14129e4..c9c77f3e 100644 --- a/examples/rocksdb-cluster/Cargo.lock +++ b/examples/rocksdb-cluster/Cargo.lock @@ -511,10 +511,9 @@ dependencies = [ ] [[package]] -name = "d-engine" -version = "0.1.3" +name = "d-engine-core" +version = "0.1.0" dependencies = [ - "arc-swap", "async-compression", "async-stream", "bincode 1.3.3", @@ -523,8 +522,8 @@ dependencies = [ "crc32fast", "crossbeam", "crossbeam-skiplist", + "d-engine-proto", "dashmap", - "flate2", "futures", "http-body", "http-body-util", @@ -532,12 +531,8 @@ dependencies = [ "memmap2", "metrics", "nanoid", - "parking_lot", "prost", "rand 0.8.5", - "rcgen", - "ring", - "rocksdb", "serde", "sha2", "tempfile", @@ -545,14 +540,55 @@ dependencies = [ "tokio", "tokio-stream", "tokio-tar", - "tokio-util", + "tonic", + "tracing", +] + +[[package]] +name = "d-engine-proto" +version = "0.2.0" +dependencies = [ + "bytes", + "prost", + "serde", "tonic", "tonic-build", - "tonic-health", "tracing", "vergen", ] +[[package]] +name = "d-engine-server" +version = "0.2.0" +dependencies = [ + "arc-swap", + "bincode 1.3.3", + "bytes", + "config", + "crc32fast", + "crossbeam", + "crossbeam-skiplist", + "d-engine-core", + "d-engine-proto", + "dashmap", + "futures", + "http-body", + "http-body-util", + "metrics", + "parking_lot", + "prost", + "rcgen", + "rocksdb", + "serde", + "tempfile", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tonic-health", + "tracing", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -644,7 +680,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", - "libz-sys", "miniz_oxide", ] @@ -1754,7 +1789,7 @@ dependencies = [ "async-trait", "bincode 2.0.1", "bytes", - "d-engine", + "d-engine-server", "metrics-exporter-prometheus", "prost", "tokio", diff --git a/examples/rocksdb-cluster/Cargo.toml b/examples/rocksdb-cluster/Cargo.toml index d2de9124..6b03b532 100644 --- a/examples/rocksdb-cluster/Cargo.toml +++ b/examples/rocksdb-cluster/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] # d-engine = "0.1.4" -d-engine = { path = "../../../d-engine", features = ["rocksdb"] } +d-engine-server = { path = "../../d-engine-server", features = ["rocksdb"] } bytes = "1.10" prost = { version = "0.13", default-features = false } diff --git a/examples/rocksdb-cluster/src/main.rs b/examples/rocksdb-cluster/src/main.rs index f61e9bdd..23b5e590 100644 --- a/examples/rocksdb-cluster/src/main.rs +++ b/examples/rocksdb-cluster/src/main.rs @@ -1,6 +1,6 @@ -use d_engine::file_io::open_file_for_append; -use d_engine::NodeBuilder; -use d_engine::{RocksDBStateMachine, RocksDBStorageEngine}; +use d_engine_server::file_io::open_file_for_append; +use d_engine_server::NodeBuilder; +use d_engine_server::{RocksDBStateMachine, RocksDBStorageEngine}; use std::env; use std::error::Error; use std::path::{Path, PathBuf}; diff --git a/examples/sled-cluster/Cargo.toml b/examples/sled-cluster/Cargo.toml index 0acdb615..f96f1944 100644 --- a/examples/sled-cluster/Cargo.toml +++ b/examples/sled-cluster/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] # d-engine = "0.1.4" -d-engine = { path = "../../../d-engine" } +d-engine-server = { path = "../../d-engine-server" } sled = { version = "0.34.7", features = [ diff --git a/examples/sled-cluster/src/main.rs b/examples/sled-cluster/src/main.rs index 46d272aa..630333a8 100644 --- a/examples/sled-cluster/src/main.rs +++ b/examples/sled-cluster/src/main.rs @@ -1,5 +1,4 @@ -use d_engine::file_io::open_file_for_append; -use d_engine::NodeBuilder; +use d_engine_server::NodeBuilder; use sled_demo::{SledStateMachine, SledStorageEngine}; use std::env; use std::error::Error; diff --git a/examples/sled-cluster/src/sled_engine_test.rs b/examples/sled-cluster/src/sled_engine_test.rs index 162b5549..f90fc616 100644 --- a/examples/sled-cluster/src/sled_engine_test.rs +++ b/examples/sled-cluster/src/sled_engine_test.rs @@ -1,11 +1,7 @@ use super::*; use async_trait::async_trait; use bytes::{Bytes, BytesMut}; -use d_engine::{ - proto::{client::write_command::Insert, common::Entry}, - storage_engine_test::{StorageEngineBuilder, StorageEngineTestSuite}, - Error, LogStore, StorageEngine, -}; +use d_engine_server::StorageEngine; use prost::Message; use std::sync::Arc; use tempfile::TempDir; @@ -79,23 +75,21 @@ async fn test_sled_performance() -> Result<(), Error> { Ok(()) } -fn create_test_command_payload(index: u64) -> d_engine::proto::common::EntryPayload { +fn create_test_command_payload(index: u64) -> common::EntryPayload { // Create a simple insert command let key = Bytes::from(format!("key_{index}").into_bytes()); let value = Bytes::from(format!("value_{index}").into_bytes()); let insert = Insert { key, value }; - let operation = d_engine::proto::client::write_command::Operation::Insert(insert); - let write_cmd = d_engine::proto::client::WriteCommand { + let operation = client::write_command::Operation::Insert(insert); + let write_cmd = client::WriteCommand { operation: Some(operation), }; let mut buffer = BytesMut::new(); write_cmd.encode(&mut buffer).expect("Failed to encode insert command"); let buffer = buffer.freeze(); - d_engine::proto::common::EntryPayload { - payload: Some(d_engine::proto::common::entry_payload::Payload::Command( - buffer, - )), + common::EntryPayload { + payload: Some(common::entry_payload::Payload::Command(buffer)), } } diff --git a/examples/sled-cluster/src/sled_state_machine.rs b/examples/sled-cluster/src/sled_state_machine.rs index 3b54e4cf..5c67e3ff 100644 --- a/examples/sled-cluster/src/sled_state_machine.rs +++ b/examples/sled-cluster/src/sled_state_machine.rs @@ -4,20 +4,20 @@ use arc_swap::ArcSwap; use async_trait::async_trait; use bincode::config; use bytes::Bytes; -use d_engine::convert::safe_kv; -use d_engine::convert::safe_vk; -use d_engine::proto::client::write_command::Delete; -use d_engine::proto::client::write_command::Insert; -use d_engine::proto::client::write_command::Operation; -use d_engine::proto::client::WriteCommand; -use d_engine::proto::common::entry_payload::Payload; -use d_engine::proto::common::Entry; -use d_engine::proto::common::LogId; -use d_engine::proto::storage::SnapshotMetadata; -use d_engine::Error; -use d_engine::SnapshotError; -use d_engine::StateMachine; -use d_engine::StorageError; +use d_engine_server::client::write_command::Delete; +use d_engine_server::client::write_command::Insert; +use d_engine_server::client::write_command::Operation; +use d_engine_server::client::WriteCommand; +use d_engine_server::common::entry_payload::Payload; +use d_engine_server::convert::safe_kv; +use d_engine_server::convert::safe_vk; +use d_engine_server::server::storage::SnapshotMetadata; +use d_engine_server::Entry; +use d_engine_server::Error; +use d_engine_server::LogId; +use d_engine_server::SnapshotError; +use d_engine_server::StateMachine; +use d_engine_server::StorageError; use parking_lot::Mutex; use prost::Message; use sled::Batch; diff --git a/examples/sled-cluster/src/sled_storage_engine.rs b/examples/sled-cluster/src/sled_storage_engine.rs index 3c56a059..833cd22b 100644 --- a/examples/sled-cluster/src/sled_storage_engine.rs +++ b/examples/sled-cluster/src/sled_storage_engine.rs @@ -1,16 +1,16 @@ use async_trait::async_trait; use bincode::config; use bytes::Bytes; -use d_engine::convert::safe_vk; -use d_engine::proto::common::Entry; -use d_engine::proto::common::LogId; -use d_engine::Error; -use d_engine::HardState; -use d_engine::LogStore; -use d_engine::MetaStore; -use d_engine::ProstError; -use d_engine::StorageEngine; -use d_engine::StorageError; +use d_engine_server::common::Entry; +use d_engine_server::common::LogId; +use d_engine_server::convert::safe_vk; +use d_engine_server::Error; +use d_engine_server::HardState; +use d_engine_server::LogStore; +use d_engine_server::MetaStore; +use d_engine_server::ProstError; +use d_engine_server::StorageEngine; +use d_engine_server::StorageError; use prost::Message; use sled::Batch; use sled::IVec; diff --git a/examples/sled-cluster/src/state_machine/sled_state_machine_test.rs b/examples/sled-cluster/src/state_machine/sled_state_machine_test.rs index bc3e6771..6e7e1818 100644 --- a/examples/sled-cluster/src/state_machine/sled_state_machine_test.rs +++ b/examples/sled-cluster/src/state_machine/sled_state_machine_test.rs @@ -15,15 +15,15 @@ use d_engine::convert::safe_kv; use d_engine::file_io::compute_checksum_from_folder_path; use d_engine::init_sled_state_machine_db; use d_engine::init_sled_storages; -use d_engine::proto::common::Entry; -use d_engine::proto::common::EntryPayload; -use d_engine::proto::common::LogId; -use d_engine::proto::storage::SnapshotMetadata; use d_engine::test_utils::generate_delete_commands; use d_engine::test_utils::generate_insert_commands; use d_engine::test_utils::reset_dbs; use d_engine::test_utils::reuse_dbs; use d_engine::StateMachine; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::LogId; +use d_engine_proto::storage::SnapshotMetadata; pub fn setup_raft_components( db_path: &str, diff --git a/examples/sled-cluster/src/storage_engine/sled_storage_engine_test.rs b/examples/sled-cluster/src/storage_engine/sled_storage_engine_test.rs index 07debe51..7f4a48e0 100644 --- a/examples/sled-cluster/src/storage_engine/sled_storage_engine_test.rs +++ b/examples/sled-cluster/src/storage_engine/sled_storage_engine_test.rs @@ -1,8 +1,4 @@ use super::*; -use d_engine::proto::common::Entry; -use d_engine::proto::common::EntryPayload; -use d_engine::proto::common::LogId; -use d_engine::proto::election::VotedFor; use d_engine::Error; use d_engine::HardState; use d_engine::LogStore; @@ -10,6 +6,10 @@ use d_engine::MetaStore; use d_engine::ProstError; use d_engine::StorageEngine; use d_engine::SystemError; +use d_engine_proto::common::Entry; +use d_engine_proto::common::EntryPayload; +use d_engine_proto::common::LogId; +use d_engine_proto::election::VotedFor; use std::ops::RangeInclusive; use std::sync::Arc; use tempfile::TempDir; diff --git a/examples/three-nodes-cluster/Cargo.toml b/examples/three-nodes-cluster/Cargo.toml index 0e02c396..94703334 100644 --- a/examples/three-nodes-cluster/Cargo.toml +++ b/examples/three-nodes-cluster/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] # d-engine = "0.1.1" -d-engine = { path = "../../../d-engine", features = ["rocksdb"] } +d-engine-server = { path = "../../d-engine-server", features = ["rocksdb"] } # d-engine = { git = "https://github.com/deventlab/d-engine.git", branch = "feature/79-snapshot" } metrics-exporter-prometheus = "0.17.2" diff --git a/examples/three-nodes-cluster/src/main.rs b/examples/three-nodes-cluster/src/main.rs index 3e075a92..2f6a4434 100644 --- a/examples/three-nodes-cluster/src/main.rs +++ b/examples/three-nodes-cluster/src/main.rs @@ -1,11 +1,11 @@ -use d_engine::file_io::open_file_for_append; -use d_engine::node::NodeBuilder; +use d_engine_server::node::NodeBuilder; // use d_engine::FileStateMachine; // use d_engine::FileStorageEngine; -use d_engine::RocksDBStateMachine; -use d_engine::RocksDBStorageEngine; +use d_engine_server::RocksDBStateMachine; +use d_engine_server::RocksDBStorageEngine; use std::env; use std::error::Error; +use std::fs::OpenOptions; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -190,3 +190,16 @@ async fn collect_tokio_metrics( } } } + +fn open_file_for_append(path: PathBuf) -> Result> { + // Create parent directories if they don't exist + if let Some(parent) = path.parent() { + if parent != Path::new("") { + std::fs::create_dir_all(parent)?; + } + } + + let log_file = OpenOptions::new().append(true).create(true).open(&path)?; + + Ok(log_file) +} From 7474535031cb1f12b916c02e7c457b409990785f Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Sat, 1 Nov 2025 22:02:23 +0800 Subject: [PATCH 21/26] feat #167: waiting for benchmark on v0.2.0 --- Makefile | 64 +- benches/d-engine-bench/Cargo.lock | 3 +- benches/d-engine-bench/Cargo.toml | 7 +- benches/d-engine-bench/src/main.rs | 24 +- d-engine-client/Cargo.toml | 11 + d-engine-client/src/kv.rs | 8 +- d-engine-client/src/lib.rs | 25 + d-engine-core/Cargo.toml | 11 + d-engine-docs/Cargo.toml | 11 + .../single-responsibility-principle.md | 31 +- .../architecture/snapshot-module-design.md | 22 +- d-engine-docs/src/docs/overview.md | 8 +- d-engine-docs/src/lib.rs | 277 +++++++ d-engine-proto/Cargo.toml | 11 + d-engine-server/Cargo.toml | 11 + d-engine-server/src/lib.rs | 56 +- d-engine-server/src/node/mod.rs | 2 +- .../adaptors/file/file_storage_engine.rs | 3 +- examples/client_usage/Cargo.lock | 728 ++---------------- examples/client_usage/Cargo.toml | 4 +- examples/client_usage/src/main.rs | 28 +- examples/rocksdb-cluster/Cargo.lock | 691 +++++++---------- examples/rocksdb-cluster/Cargo.toml | 4 +- examples/rocksdb-cluster/src/main.rs | 17 +- examples/sled-cluster/Cargo.toml | 1 - examples/sled-cluster/src/converters.rs | 47 ++ examples/sled-cluster/src/lib.rs | 2 + examples/sled-cluster/src/main.rs | 16 + examples/sled-cluster/src/sled_engine_test.rs | 75 +- .../sled-cluster/src/sled_state_machine.rs | 79 +- .../sled-cluster/src/sled_storage_engine.rs | 65 +- 31 files changed, 1070 insertions(+), 1272 deletions(-) create mode 100644 examples/sled-cluster/src/converters.rs diff --git a/Makefile b/Makefile index 0cd17e83..ad8b878b 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,8 @@ .PHONY: help all check fmt fmt-check clippy clippy-fix test test-unit \ test-integration test-doc bench clean doc build build-release \ pre-release install check-env audit deny troubleshoot \ - test-crate test-examples test-detailed + test-crate test-examples test-detailed \ + docs docs-all docs-check docs-check-all # ============================================================================ # CONFIGURATION SECTION @@ -64,6 +65,12 @@ help: @echo " make fmt-fix # Auto-fix formatting issues" @echo " make clippy-fix # Apply clippy suggestions" @echo "" + @echo " $(YELLOW)Documentation:$(NC)" + @echo " make docs # Build and open documentation in browser" + @echo " make docs-check # Verify docs compile without warnings" + @echo " make docs-private # Build docs with private items visible" + @echo " make docs-crate CRATE=name # Build docs for specific crate" + @echo "" @echo " $(YELLOW)Testing:$(NC)" @echo " make test # Run unit + integration tests" @echo " make test-detailed # Run tests with detailed failure output" @@ -76,6 +83,7 @@ help: @echo "" @echo " $(YELLOW)Maintenance:$(NC)" @echo " make clean # Remove all build artifacts" + @echo " make docs-clean # Remove generated documentation" @echo " make audit # Check for security vulnerabilities" @echo " make troubleshoot # Diagnostic information" @echo "" @@ -289,17 +297,57 @@ bench: check-workspace # DOCUMENTATION # ============================================================================ -## doc Generate API documentation and open in browser -doc: check-workspace - @echo "$(BLUE)Generating API documentation...$(NC)" +## docs Generate API documentation and open in browser +docs: check-workspace + @echo "$(BLUE)Generating API documentation for all workspace crates...$(NC)" + @$(CARGO) doc --workspace --no-deps --open + @echo "$(GREEN)✓ Documentation generated and opened$(NC)" + +## docs-all Generate documentation with all features enabled +docs-all: check-workspace + @echo "$(BLUE)Generating API documentation (all features)...$(NC)" @$(CARGO) doc --workspace --all-features --no-deps --open @echo "$(GREEN)✓ Documentation generated and opened$(NC)" -## doc-check Generate documentation without opening browser -doc-check: check-workspace +## docs-check Check documentation without opening browser (CI-safe) +docs-check: check-workspace @echo "$(BLUE)Checking documentation generation...$(NC)" - @$(CARGO) doc --workspace --all-features --no-deps - @echo "$(GREEN)✓ Documentation generated successfully$(NC)" + @RUSTDOCFLAGS="-D warnings" $(CARGO) doc --workspace --no-deps + @echo "$(GREEN)✓ Documentation compiled without warnings$(NC)" + +## docs-check-all Check documentation with all features and strict warnings +docs-check-all: check-workspace + @echo "$(BLUE)Checking documentation (all features, strict mode)...$(NC)" + @RUSTDOCFLAGS="-D warnings" $(CARGO) doc --workspace --all-features --no-deps + @echo "$(GREEN)✓ Documentation compiled without warnings$(NC)" + +## docs-private Generate documentation including private items (architecture) +docs-private: check-workspace + @echo "$(BLUE)Generating documentation with private items...$(NC)" + @$(CARGO) doc --workspace --no-deps --document-private-items --open + @echo "$(GREEN)✓ Documentation generated with private items$(NC)" + +## docs-crate Generate documentation for a single crate (usage: make docs-crate CRATE=d-engine-server) +docs-crate: check-workspace +ifndef CRATE + @echo "$(RED)Error: CRATE variable not set. Usage: make docs-crate CRATE=crate-name$(NC)" + @exit 1 +endif + @echo "$(BLUE)Generating documentation for crate: $(CRATE)...$(NC)" + @$(CARGO) doc -p $(CRATE) --no-deps --open + @echo "$(GREEN)✓ Documentation generated for $(CRATE)$(NC)" + +## docs-clean Remove generated documentation artifacts +docs-clean: + @echo "$(BLUE)Cleaning documentation artifacts...$(NC)" + @rm -rf target/doc + @echo "$(GREEN)✓ Documentation cleaned$(NC)" + +## doc (Deprecated alias) Use 'docs' instead +doc: docs + +## doc-check (Deprecated alias) Use 'docs-check' instead +doc-check: docs-check # ============================================================================ # SECURITY & DEPENDENCY AUDITING diff --git a/benches/d-engine-bench/Cargo.lock b/benches/d-engine-bench/Cargo.lock index 7d66d618..c1230a1e 100644 --- a/benches/d-engine-bench/Cargo.lock +++ b/benches/d-engine-bench/Cargo.lock @@ -301,13 +301,12 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "d-engine-bench" -version = "0.1.4" +version = "0.2.0" dependencies = [ "atomic-shim", "bytes", "clap", "d-engine-client", - "d-engine-proto", "futures", "hdrhistogram", "rand", diff --git a/benches/d-engine-bench/Cargo.toml b/benches/d-engine-bench/Cargo.toml index 78a53346..48ec7ef3 100644 --- a/benches/d-engine-bench/Cargo.toml +++ b/benches/d-engine-bench/Cargo.toml @@ -1,12 +1,11 @@ [package] name = "d-engine-bench" -version = "0.1.4" -edition = "2021" +version = "0.2.0" +edition = "2024" [dependencies] d-engine-client = { path = "../../d-engine-client" } -d-engine-proto = { path = "../../d-engine-proto" } -# d-engine = { git = "https://github.com/deventlab/d-engine.git", branch = "feature/79-snapshot" } + tokio = { version = "1.44", features = ["full"] } clap = { version = "4.5", features = ["derive"] } diff --git a/benches/d-engine-bench/src/main.rs b/benches/d-engine-bench/src/main.rs index a17e3e49..13f6e84a 100644 --- a/benches/d-engine-bench/src/main.rs +++ b/benches/d-engine-bench/src/main.rs @@ -1,7 +1,7 @@ -use std::sync::atomic::AtomicU64; -use std::sync::atomic::Ordering; use std::sync::Arc; use std::sync::Mutex; +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering; use std::time::Duration; use std::time::Instant; @@ -10,11 +10,11 @@ use clap::Subcommand; use d_engine_client::Client; use d_engine_client::ClientApiError; use d_engine_client::ClientBuilder; -use d_engine_proto::client::ReadConsistencyPolicy; +use d_engine_client::protocol::ReadConsistencyPolicy; use hdrhistogram::Histogram; -use rand::distributions::Alphanumeric; use rand::Rng; use rand::SeedableRng; +use rand::distributions::Alphanumeric; use tokio::sync::Semaphore; #[derive(Parser, Debug, Clone)] @@ -283,10 +283,10 @@ mod tests { #[test] fn test_sequential_keys() { // Test the order keys from high to low - assert_eq!(generate_key(true, 6, 0), "999999"); - assert_eq!(generate_key(true, 6, 1), "999998"); - assert_eq!(generate_key(true, 3, 0), "999"); - assert_eq!(generate_key(true, 3, 1), "998"); + assert_eq!(generate_prefixed_key(true, 6, 0), "999999"); + assert_eq!(generate_prefixed_key(true, 6, 1), "999998"); + assert_eq!(generate_prefixed_key(true, 3, 0), "999"); + assert_eq!(generate_prefixed_key(true, 3, 1), "998"); } #[test] @@ -300,17 +300,17 @@ mod tests { #[test] fn test_large_index() { // Test large index values ​​(will saturate to minimum value) - assert_eq!(generate_key(true, 3, 9999), "000"); // Saturate to minimum value + assert_eq!(generate_prefixed_key(true, 3, 9999), "000"); // Saturate to minimum value } #[test] fn test_random_keys() { // Test the determinism and randomness of the random key - let key1 = generate_key(false, 8, 42); - let key2 = generate_key(false, 8, 42); + let key1 = generate_prefixed_key(false, 8, 42); + let key2 = generate_prefixed_key(false, 8, 42); assert_eq!(key1, key2); - let key3 = generate_key(false, 8, 43); + let key3 = generate_prefixed_key(false, 8, 43); assert_ne!(key1, key3); } } diff --git a/d-engine-client/Cargo.toml b/d-engine-client/Cargo.toml index 6ae58f0a..95420a91 100644 --- a/d-engine-client/Cargo.toml +++ b/d-engine-client/Cargo.toml @@ -2,6 +2,17 @@ name = "d-engine-client" version = "0.2.0" edition = "2024" +authors = ["Joshua Chi "] +description = "Client library for connecting to d-engine Raft consensus servers" +homepage = "https://github.com/deventlab/d-engine" +repository = "https://github.com/deventlab/d-engine" +license = "MIT OR Apache-2.0" +keywords = ["raft", "consensus", "client", "async"] +categories = ["asynchronous", "network-programming"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] [dependencies] d-engine-proto = { workspace = true } diff --git a/d-engine-client/src/kv.rs b/d-engine-client/src/kv.rs index ed77e9b4..26358112 100644 --- a/d-engine-client/src/kv.rs +++ b/d-engine-client/src/kv.rs @@ -41,7 +41,8 @@ impl KvClient { /// /// # Errors /// - [`crate::ClientApiError::Network`] on network failures - /// - [`crate::ClientApiError::InvalidResponse`] for malformed server responses + /// - [`crate::ClientApiError::Protocol`] for protocol errors + /// - [`crate::ClientApiError::Storage`] for server-side storage errors pub async fn put( &self, key: impl AsRef<[u8]>, @@ -88,8 +89,9 @@ impl KvClient { /// (e.g. `String`, `&str`, `Vec`) /// /// # Errors - /// - [`Error::FailedToSendWriteRequestError`] if unable to reach the leader node - /// - [`Error::InvalidResponse`] for malformed server responses + /// - [`crate::ClientApiError::Network`] if unable to reach the leader node + /// - [`crate::ClientApiError::Protocol`] for protocol errors + /// - [`crate::ClientApiError::Storage`] for server-side storage errors pub async fn delete( &self, key: impl AsRef<[u8]>, diff --git a/d-engine-client/src/lib.rs b/d-engine-client/src/lib.rs index b42892c7..e36b95ca 100644 --- a/d-engine-client/src/lib.rs +++ b/d-engine-client/src/lib.rs @@ -58,6 +58,31 @@ pub use config::*; pub use error::*; pub use kv::*; pub use pool::*; +pub use utils::*; + +// ==================== Protocol Types (Essential for Public API) ==================== + +/// Protocol types needed for client operations +/// +/// These types are used in the public API and must be imported for client usage: +/// - `ClientResult`: Response type from read operations +/// - `ReadConsistencyPolicy`: Consistency guarantees for reads +/// - `WriteCommand`: Write operation specifications +pub mod protocol { + pub use d_engine_proto::client::{ClientResult, ReadConsistencyPolicy, WriteCommand}; +} + +/// Cluster management protocol types +/// +/// Types required for cluster administration operations: +/// - `NodeMeta`: Cluster node metadata +/// - `NodeStatus`: Node status enumeration +pub mod cluster_types { + pub use d_engine_proto::common::NodeStatus; + pub use d_engine_proto::server::cluster::NodeMeta; +} + +// ==================== Hide Implementation Details ==================== pub(crate) use proto::*; #[cfg(test)] diff --git a/d-engine-core/Cargo.toml b/d-engine-core/Cargo.toml index 2d6e753b..687baa82 100644 --- a/d-engine-core/Cargo.toml +++ b/d-engine-core/Cargo.toml @@ -2,6 +2,17 @@ name = "d-engine-core" version = "0.1.0" edition = "2024" +authors = ["Joshua Chi "] +description = "Core Raft consensus algorithm implementation and traits" +homepage = "https://github.com/deventlab/d-engine" +repository = "https://github.com/deventlab/d-engine" +license = "MIT OR Apache-2.0" +keywords = ["raft", "consensus", "algorithm"] +categories = ["algorithms", "concurrency"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] [features] diff --git a/d-engine-docs/Cargo.toml b/d-engine-docs/Cargo.toml index 5607f2b0..c7bce5ae 100644 --- a/d-engine-docs/Cargo.toml +++ b/d-engine-docs/Cargo.toml @@ -2,6 +2,17 @@ name = "d-engine-docs" version = "0.2.0" edition = "2024" +authors = ["Joshua Chi "] +description = "Documentation and guides for d-engine Raft consensus engine" +homepage = "https://github.com/deventlab/d-engine" +repository = "https://github.com/deventlab/d-engine" +license = "MIT OR Apache-2.0" +keywords = ["raft", "consensus", "documentation"] +categories = ["documentation"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] [dependencies] d-engine-server = { workspace = true } diff --git a/d-engine-docs/src/docs/architecture/single-responsibility-principle.md b/d-engine-docs/src/docs/architecture/single-responsibility-principle.md index 249c36c6..b6434a2b 100644 --- a/d-engine-docs/src/docs/architecture/single-responsibility-principle.md +++ b/d-engine-docs/src/docs/architecture/single-responsibility-principle.md @@ -5,18 +5,17 @@ This project strictly follows the **Single Responsibility Principle (SRP)** to e ### **1. Core Design Philosophy** - **Main Loop Responsibility**: - - The `main_loop` (in `src/core/raft.rs`) **only orchestrates event routing**. It: - - 1. Listens for events (e.g., RPCs, timers). - 2. Delegates handling to the **current role** (e.g., `LeaderState`, `FollowerState`). - 3. Manages role transitions (e.g., `Leader → Follower`). + + The `main_loop` (in `src/core/raft.rs`) **only orchestrates event routing**. It: + 1. Listens for events (e.g., RPCs, timers). + 2. Delegates handling to the **current role** (e.g., `LeaderState`, `FollowerState`). + 3. Manages role transitions (e.g., `Leader → Follower`). + - **Role-Specific Logic**: - - Each role (`LeaderState`, `FollowerState`, etc.) **owns its state and behavior**. For example: - - - `LeaderState` handles log replication and heartbeat management. - - `FollowerState` processes leader requests and election timeouts. + + Each role (`LeaderState`, `FollowerState`, etc.) **owns its state and behavior**. For example: + - `LeaderState` handles log replication and heartbeat management. + - `FollowerState` processes leader requests and election timeouts. --- @@ -120,7 +119,7 @@ impl FollowerState { The codebase enforces SRP through role-specific modules: -```ignore +```text src/core/raft_role/ ├── leader_state.rs # Leader-only logic (e.g., log replication) ├── follower_state.rs # Follower-only logic (e.g., election timeout) @@ -137,9 +136,9 @@ When extending the project: 1. **Identify Ownership**: Should the logic belong to a role, the main loop, or a utility module? 2. **Avoid Hybrid Roles**: Never create a `LeaderFollowerHybridState`. Use role transitions instead. 3. **Example**: Adding a "Learner" role: - - Create `learner_state.rs` with Learner-specific logic. - - Update `main_loop` to handle `Role::Learner`. - - Add `RoleEvent::BecomeLearner` for transitions. + - Create `learner_state.rs` with Learner-specific logic. + - Update `main_loop` to handle `Role::Learner`. + - Add `RoleEvent::BecomeLearner` for transitions. --- @@ -149,4 +148,4 @@ When extending the project: - **Safer Changes**: Modifying `FollowerState` won’t accidentally break `LeaderState`. - **Protocol Compliance**: Raft requires strict role separation; SRP enforces this naturally. -By following these rules, your contributions will align with the project’s design philosophy. \ No newline at end of file +By following these rules, your contributions will align with the project’s design philosophy. diff --git a/d-engine-docs/src/docs/architecture/snapshot-module-design.md b/d-engine-docs/src/docs/architecture/snapshot-module-design.md index 75ae212a..198e7347 100644 --- a/d-engine-docs/src/docs/architecture/snapshot-module-design.md +++ b/d-engine-docs/src/docs/architecture/snapshot-module-design.md @@ -171,23 +171,23 @@ if config.max_bandwidth_mbps > 0 { #### Generating a new snapshot: -1. [**StateMachine**] Generate new DB based on the temporary file provided by the [**StateMachineHandler**] → -2. [**StateMachine**] Generate data → -3. [**StateMachine**] Dump current DB into the new DB → -4. [**StateMachineHandler**] Verify policy conditions and finalize the snapshot and updating the snapshot version. +1. \[**StateMachine**\] Generate new DB based on the temporary file provided by the \[**StateMachineHandler**\] → +2. \[**StateMachine**\] Generate data → +3. \[**StateMachine**\] Dump current DB into the new DB → +4. \[**StateMachineHandler**\] Verify policy conditions and finalize the snapshot and updating the snapshot version. #### Applying a snapshot: -1. [**StateMachineHandler**] Snapshot chunk reception and validation → -2. [**StateMachineHandler**] Write chunks into a temporary file until success → -3. [**StateMachineHandler**] Error handling and sends error response back to the sender and terminates the process → -4. After all chunks have been successfully processed and validated, the [**StateMachineHandler**] finalizes the snapshot → -5. [**StateMachineHandler**] Passing the snapshot file to the [**StateMachine**] → -6. [**StateMachine**] Apply Snapshot and do online replacement - replacing the old state with the new one based on the snapshot. +1. \[**StateMachineHandler**\] Snapshot chunk reception and validation → +2. \[**StateMachineHandler**\] Write chunks into a temporary file until success → +3. \[**StateMachineHandler**\] Error handling and sends error response back to the sender and terminates the process → +4. After all chunks have been successfully processed and validated, the \[**StateMachineHandler**\] finalizes the snapshot → +5. \[**StateMachineHandler**\] Passing the snapshot file to the \[**StateMachine**\] → +6. \[**StateMachine**\] Apply Snapshot and do online replacement - replacing the old state with the new one based on the snapshot. #### Cleaning up old snapshots: -[**StateMachineHandler**] automatically maintains old snapshots according to version policies, while the **StateMachine** is not aware of it. +\[**StateMachineHandler**\] automatically maintains old snapshots according to version policies, while the **StateMachine** is not aware of it. ## **Purge Log Design** diff --git a/d-engine-docs/src/docs/overview.md b/d-engine-docs/src/docs/overview.md index 1e771cf7..914d5fe3 100644 --- a/d-engine-docs/src/docs/overview.md +++ b/d-engine-docs/src/docs/overview.md @@ -24,10 +24,10 @@ ## Quick Start -```no_run -use d_engine::NodeBuilder; -use d_engine::FileStorageEngine; -use d_engine::FileStateMachine; +```ignore +use d_engine_server::NodeBuilder; +use d_engine_server::FileStorageEngine; +use d_engine_server::FileStateMachine; use tokio::sync::watch; use tracing::error; use tracing::info; diff --git a/d-engine-docs/src/lib.rs b/d-engine-docs/src/lib.rs index ca422a9c..233e223d 100644 --- a/d-engine-docs/src/lib.rs +++ b/d-engine-docs/src/lib.rs @@ -1 +1,278 @@ +#![warn(missing_docs)] + +//! # d-engine - A Lightweight Raft Consensus Engine +//! +//! Welcome to d-engine, a robust, production-ready Raft consensus engine written in Rust. +//! +//! ## Overview +//! +//! d-engine provides a battle-tested Raft implementation for building distributed systems with: +//! - **Fault tolerance**: Automatic leader election and recovery +//! - **Strong consistency**: Ensures all replicas are identical +//! - **Modularity**: Pluggable storage, state machines, and networking +//! - **Performance**: Optimized for throughput and latency +//! +//! ## Crate Organization +//! +//! The d-engine workspace consists of multiple specialized crates: +//! +//! | Crate | Purpose | Use Case | +//! |-------|---------|----------| +//! | **d-engine-proto** | Protocol definitions using Prost | Shared protocol between components | +//! | **d-engine-core** | Core Raft algorithm & traits | Understanding Raft consensus logic | +//! | **d-engine-client** | Client library for applications | Building client applications | +//! | **d-engine-server** | Server runtime implementation | Running d-engine nodes | +//! | **d-engine-docs** | Guides, tutorials, and architecture | Learning and reference | +//! +//! ## Quick Start: 5 Minutes +//! +//! ### Running a Cluster +//! +//! ```bash +//! # Terminal 1: Start first node +//! cargo run --example three-nodes-cluster -- --node-id 1 --port 9000 +//! +//! # Terminal 2: Start second node +//! cargo run --example three-nodes-cluster -- --node-id 2 --port 9001 +//! +//! # Terminal 3: Start third node +//! cargo run --example three-nodes-cluster -- --node-id 3 --port 9002 +//! ``` +//! +//! ### Using the Client +//! +//! ```ignore +//! use d_engine_client::ClientBuilder; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { +//! // Create a client +//! let client = ClientBuilder::new() +//! .endpoints(vec!["http://127.0.0.1:9000".to_string()]) +//! .build() +//! .await?; +//! +//! // Perform operations +//! client.put(b"key", b"value").await?; +//! let value = client.get(b"key").await?; +//! println!("Value: {:?}", value); +//! +//! Ok(()) +//! } +//! ``` +//! +//! ## API Documentation +//! +//! ### For Server Operators +//! +//! - **[d_engine_server API](../d_engine_server/index.html)** - Server runtime and node management +//! - How to [customize storage engines](./docs/server_guide/customize-storage-engine.html) +//! - How to [implement state machines](./docs/server_guide/customize-state-machine.html) +//! - [Performance tuning guide](./docs/performance/throughput-optimization-guide.html) +//! +//! ### For Client Developers +//! +//! - **[d_engine_client API](../d_engine_client/index.html)** - Client library for applications +//! - [Read consistency policies](./docs/client_guide/read_consistency.html) +//! - Error handling and retry strategies +//! - Connection pooling and timeouts +//! +//! ### For System Designers +//! +//! - **[d_engine_core API](../d_engine_core/index.html)** - Core Raft implementation +//! - Understanding trait-based design +//! - Custom storage backend development +//! - State machine implementation guide +//! +//! ## Architecture Documentation +//! +//! Deep dive into d-engine's design and implementation: +//! +//! - [Raft Roles and State Transitions](./docs/architecture/raft-role.html) +//! - How leader, candidate, and follower states work +//! - [Election Design](./docs/architecture/election-design.html) +//! - Why randomized timeouts prevent split-brain +//! - [Raft Log Persistence Architecture](./docs/architecture/raft-log-persistence-architecture.html) +//! - How log entries are persisted and recovered +//! - [Snapshot Module Design](./docs/architecture/snapshot-module-design.html) +//! - Snapshots for recovery optimization +//! - [New Node Join Architecture](./docs/architecture/new-node-join-architecture.html) +//! - Dynamic cluster membership changes +//! - [Error Handling Design Principles](./docs/architecture/error-handling-design-principles.html) +//! - How errors are categorized and handled +//! - [Single Responsibility Principle](./docs/architecture/single-responsibility-principle.html) +//! - Design philosophy behind modularity +//! +//! ## Key Concepts +//! +//! ### Raft Protocol Basics +//! +//! Raft solves the consensus problem: keeping distributed systems in sync despite failures. +//! +//! **Three roles**: +//! - **Leader**: Accepts client requests and replicates logs +//! - **Candidate**: Competing to become leader (during elections) +//! - **Follower**: Replicates leader's logs +//! +//! **Key mechanisms**: +//! - **Log replication**: Leader sends entries to followers sequentially +//! - **Election**: When leader fails, followers start election with randomized timeouts +//! - **Snapshots**: Periodic snapshots reduce recovery time +//! - **Joint consensus**: Safe configuration changes using overlapping quorums +//! +//! ### Read Consistency +//! +//! d-engine supports different consistency levels: +//! - **Strong consistency**: Read from leader only (guarantees latest value) +//! - **Eventual consistency**: Read from any replica (may be stale) +//! +//! See [consistency policies guide](./docs/client_guide/read_consistency.html) for details. +//! +//! ### Storage Abstraction +//! +//! Pluggable backends support different requirements: +//! - **File-based storage**: Simple, local-only persistence +//! - **RocksDB**: High-performance embedded database +//! - **Custom storage**: Implement `LogStore` + `StateSnapshot` traits +//! +//! ## Common Patterns +//! +//! ### Implementing a Custom Storage Engine +//! +//! See [Customize Storage Engine guide](./docs/server_guide/customize-storage-engine.html) for: +//! - Trait requirements +//! - Persistence guarantees +//! - Concurrency considerations +//! +//! ### Implementing a Custom State Machine +//! +//! See [Customize State Machine guide](./docs/server_guide/customize-state-machine.html) for: +//! - Processing write commands +//! - Snapshot serialization +//! - Recovery from snapshots +//! +//! ### Configuring Log Purging +//! +//! See [Log Purge Executor guide](./docs/server_guide/customize-raft-log-purge-executor.html) for: +//! - Automatic log cleanup +//! - Retention policies +//! - Performance impact +//! +//! ## Performance +//! +//! d-engine is optimized for: +//! - **Throughput**: Batching and pipelining for high request rates +//! - **Latency**: Single-digit millisecond write latencies +//! - **Scalability**: Efficient leader-based replication +//! +//! See [Performance Tuning Guide](./docs/performance/throughput-optimization-guide.html) for: +//! - Configuration recommendations +//! - Benchmarking methodology +//! - Common bottlenecks and solutions +//! +//! ## Building Documentation +//! +//! ### Generate All Workspace Documentation +//! +//! ```bash +//! # Generate all crate docs (recommended) +//! cargo doc --workspace --no-deps --open +//! +//! # With all features enabled +//! cargo doc --workspace --all-features --no-deps --open +//! +//! # Include private items (for architecture understanding) +//! cargo doc --workspace --no-deps --document-private-items --open +//! ``` +//! +//! ### Generate Specific Crate Documentation +//! +//! ```bash +//! # Server crate only +//! cargo doc -p d-engine-server --no-deps --open +//! +//! # Client crate only +//! cargo doc -p d-engine-client --no-deps --open +//! +//! # Core Raft implementation +//! cargo doc -p d-engine-core --no-deps --open +//! ``` +//! +//! ### Using Makefile Commands +//! +//! ```bash +//! make docs # Build documentation +//! make docs-open # Build and open in browser +//! make docs-check # Verify docs compile without warnings +//! ``` +//! +//! ## Examples +//! +//! The `examples/` directory contains complete, runnable examples: +//! +//! - **three-nodes-cluster**: Basic 3-node cluster setup +//! - **sled-cluster**: Using Sled embedded database for storage +//! - **rocksdb-cluster**: Using RocksDB for storage +//! - **client_usage**: Complete client application example +//! +//! Run any example: +//! ```bash +//! cargo run --example three-nodes-cluster -- --node-id 1 --port 9000 +//! ``` +//! +//! ## Testing +//! +//! d-engine includes comprehensive tests: +//! +//! ```bash +//! # Run all tests +//! cargo test --workspace +//! +//! # Run tests for specific crate +//! cargo test -p d-engine-server +//! +//! # Run with output +//! cargo test --workspace -- --nocapture +//! +//! # Run integration tests +//! cargo test --test '*' --workspace +//! ``` +//! +//! ## Troubleshooting +//! +//! ### Common Issues +//! +//! **Q: Cluster won't form** +//! - Ensure all nodes have different IDs +//! - Check network connectivity between nodes +//! - Verify ports are not in use +//! +//! **Q: High latency** +//! - Check network latency between nodes +//! - Verify storage I/O performance +//! - Consider batching multiple requests +//! +//! **Q: Memory usage growing** +//! - Check log purging is configured +//! - Verify snapshots are being taken +//! - Monitor state machine memory +//! +//! See [architecture documentation](./docs/architecture/index.html) for deeper understanding. +//! +//! ## Contributing +//! +//! Contributions welcome! Areas of interest: +//! - Performance optimizations +//! - Additional storage backends +//! - Protocol improvements +//! - Documentation and examples +//! +//! ## License +//! +//! Licensed under either of: +//! - Apache License, Version 2.0 (LICENSE-APACHE or ) +//! - MIT license (LICENSE-MIT or ) +//! +//! at your option. + pub mod docs; diff --git a/d-engine-proto/Cargo.toml b/d-engine-proto/Cargo.toml index 0cdf1c0d..d34d7c26 100644 --- a/d-engine-proto/Cargo.toml +++ b/d-engine-proto/Cargo.toml @@ -2,6 +2,17 @@ name = "d-engine-proto" version = "0.2.0" edition = "2021" +authors = ["Joshua Chi "] +description = "Protocol definitions and message types for d-engine using Prost" +homepage = "https://github.com/deventlab/d-engine" +repository = "https://github.com/deventlab/d-engine" +license = "MIT OR Apache-2.0" +keywords = ["raft", "protobuf", "grpc"] +categories = ["encoding", "network-programming"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] [dependencies] # prost = { workspace = true } diff --git a/d-engine-server/Cargo.toml b/d-engine-server/Cargo.toml index 910b5477..bc2fa338 100644 --- a/d-engine-server/Cargo.toml +++ b/d-engine-server/Cargo.toml @@ -2,6 +2,17 @@ name = "d-engine-server" version = "0.2.0" edition = "2024" +authors = ["Joshua Chi "] +description = "Production-ready Raft consensus engine server and runtime" +homepage = "https://github.com/deventlab/d-engine" +repository = "https://github.com/deventlab/d-engine" +license = "MIT OR Apache-2.0" +keywords = ["raft", "consensus", "distributed-systems", "async"] +categories = ["asynchronous", "concurrency", "network-programming"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] [features] test-utils = ["mockall", "tokio-tar", "async-compression", "nanoid"] diff --git a/d-engine-server/src/lib.rs b/d-engine-server/src/lib.rs index 11cde340..9604c2d0 100644 --- a/d-engine-server/src/lib.rs +++ b/d-engine-server/src/lib.rs @@ -9,12 +9,12 @@ //! - Protocol definitions (re-exported from `d-engine-proto`) //! //! ## Quick Start -//! ```no_run +//! ```ignore //! use d_engine_server::{NodeBuilder, FileStorageEngine, FileStateMachine}; //! use std::sync::Arc; //! use std::path::PathBuf; //! -//! #[tokio::main] +//! #[tokio::main(flavor = "current_thread")] //! async fn main() -> Result<(), Box> { //! let (tx, rx) = tokio::sync::watch::channel(()); //! @@ -81,18 +81,56 @@ pub use storage::{RocksDBStateMachine, RocksDBStorageEngine}; // -------------------- Extension Traits -------------------- -/// Storage trait for custom implementations +/// Storage trait for implementing custom storage backends /// -/// Implement this to create your own storage backend. +/// Implement this trait to create your own storage engine. pub use d_engine_core::{StateMachine, StorageEngine}; -// -------------------- Common Types -------------------- +/// Log storage trait +pub use d_engine_core::LogStore; -/// Raft error types -pub use d_engine_core::{Error as RaftError, Result, StorageError}; +/// Metadata storage trait +pub use d_engine_core::MetaStore; -// Protocol types (minimal set for custom storage implementations) -pub use d_engine_proto::common::{Entry, LogId}; +// -------------------- Error Types -------------------- + +/// Unified error type for all d-engine operations +pub use d_engine_core::Error; + +/// Unified result type (equivalent to Result) +pub use d_engine_core::Result; + +/// Storage-specific error type +pub use d_engine_core::StorageError; + +/// Snapshot operation error type +pub use d_engine_core::SnapshotError; + +/// Protocol buffer error type +pub use d_engine_core::ProstError; + +/// Hard state (Raft persistent state: term, voted_for, log) +pub use d_engine_core::HardState; + +// -------------------- Data Types -------------------- + +/// Common Raft protocol types +pub mod common { + // Basic types used in Raft consensus protocol + pub use d_engine_proto::common::{Entry, EntryPayload, LogId, entry_payload}; +} + +/// Client protocol types +pub mod client { + // Client write command types for custom business logic + pub use d_engine_proto::client::{WriteCommand, write_command}; +} + +/// Server storage protocol types +pub mod server_storage { + // Server storage protocol types (snapshots, replication) + pub use d_engine_proto::server::storage::SnapshotMetadata; +} // ==================== Internal API (Hidden) ==================== mod membership; diff --git a/d-engine-server/src/node/mod.rs b/d-engine-server/src/node/mod.rs index bc213b22..fe14244a 100644 --- a/d-engine-server/src/node/mod.rs +++ b/d-engine-server/src/node/mod.rs @@ -1,7 +1,7 @@ //! Raft node container and lifecycle management. //! //! The [`Node`] struct acts as a host for a Raft consensus participant, -//! coordinating between the core protocol implementation ([`crate::raft::Raft`]) +//! coordinating between the core protocol implementation (provided by `d-engine-core`) //! and external subsystems: //! //! ## Key Responsibilities diff --git a/d-engine-server/src/storage/adaptors/file/file_storage_engine.rs b/d-engine-server/src/storage/adaptors/file/file_storage_engine.rs index 77dea8da..dbedff74 100644 --- a/d-engine-server/src/storage/adaptors/file/file_storage_engine.rs +++ b/d-engine-server/src/storage/adaptors/file/file_storage_engine.rs @@ -66,7 +66,8 @@ pub struct FileMetaStore { /// /// # Performance /// -/// Suitable for development and testing. For production, use [`RocksDBStorageEngine`]. +/// Suitable for development and testing. For production, consider using RocksDB storage engine +/// via the `rocksdb` feature. #[derive(Debug)] pub struct FileStorageEngine { log_store: Arc, diff --git a/examples/client_usage/Cargo.lock b/examples/client_usage/Cargo.lock index c9b798f0..4e1a8239 100644 --- a/examples/client_usage/Cargo.lock +++ b/examples/client_usage/Cargo.lock @@ -2,48 +2,21 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "anstream" version = "0.6.21" @@ -106,19 +79,6 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" -[[package]] -name = "async-compression" -version = "0.4.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" -dependencies = [ - "compression-codecs", - "compression-core", - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -222,56 +182,17 @@ dependencies = [ "tower-service", ] -[[package]] -name = "backtrace" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link", -] - [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bytes" @@ -284,9 +205,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.41" +version = "1.2.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ "find-msvc-tools", "shlex", @@ -294,15 +215,15 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.49" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", "clap_derive", @@ -310,9 +231,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.49" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstream", "anstyle", @@ -344,44 +265,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" -[[package]] -name = "compression-codecs" -version = "0.4.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" -dependencies = [ - "compression-core", - "flate2", - "memchr", -] - -[[package]] -name = "compression-core" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" - -[[package]] -name = "config" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" -dependencies = [ - "nom", - "pathdiff", - "serde", - "toml", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - [[package]] name = "crc32fast" version = "1.5.0" @@ -392,166 +275,55 @@ dependencies = [ ] [[package]] -name = "crossbeam" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-skiplist" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "d-engine" -version = "0.1.3" +name = "d-engine-client" +version = "0.2.0" dependencies = [ "arc-swap", - "async-compression", - "async-stream", - "bincode", "bytes", - "config", - "crc32fast", - "crossbeam", - "crossbeam-skiplist", - "dashmap", - "flate2", + "d-engine-proto", "futures", - "http-body", - "http-body-util", - "lru", - "memmap2", - "metrics", - "nanoid", - "parking_lot", - "prost", "rand", - "rcgen", - "ring", "serde", - "sha2", - "tempfile", - "thiserror", "tokio", "tokio-stream", - "tokio-tar", - "tokio-util", "tonic", - "tonic-build", "tonic-health", "tracing", - "vergen", ] [[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +name = "d-engine-proto" +version = "0.2.0" dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "bytes", + "prost", + "serde", + "tonic", + "tonic-build", + "tracing", + "vergen", ] [[package]] name = "dengine-client-demo" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "clap", - "d-engine", + "d-engine-client", "env_logger", "tokio", ] [[package]] name = "deranged" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", ] -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - [[package]] name = "either" version = "1.15.0" @@ -593,18 +365,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "filetime" -version = "0.2.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.60.2", -] - [[package]] name = "find-msvc-tools" version = "0.1.4" @@ -619,12 +379,11 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", - "libz-sys", "miniz_oxide", ] @@ -634,12 +393,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "futures" version = "0.3.31" @@ -729,16 +482,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.14.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getrandom" version = "0.2.16" @@ -747,27 +490,21 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", ] -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - [[package]] name = "h2" version = "0.4.12" @@ -780,7 +517,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.11.4", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -793,23 +530,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - [[package]] name = "hashbrown" version = "0.16.0" @@ -952,30 +672,19 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", ] -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -998,28 +707,6 @@ version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" -[[package]] -name = "libredox" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" -dependencies = [ - "bitflags 2.9.4", - "libc", - "redox_syscall 0.5.18", -] - -[[package]] -name = "libz-sys" -version = "1.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -1041,15 +728,6 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" -[[package]] -name = "lru" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0281c2e25e62316a5c9d98f2d2e9e95a37841afdaf4383c177dbb5c1dfab0568" -dependencies = [ - "hashbrown 0.15.5", -] - [[package]] name = "matchit" version = "0.7.3" @@ -1062,37 +740,12 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" -[[package]] -name = "memmap2" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" -dependencies = [ - "libc", -] - -[[package]] -name = "metrics" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dea7ac8057892855ec285c440160265225438c3c45072613c25a4b26e98ef5" -dependencies = [ - "ahash", - "portable-atomic", -] - [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1105,13 +758,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -1120,25 +773,6 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "num-conv" version = "0.1.0" @@ -1154,15 +788,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -1171,9 +796,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "parking_lot" @@ -1193,27 +818,11 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "pem" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64", - "serde_core", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -1227,7 +836,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.11.4", + "indexmap 2.12.0", ] [[package]] @@ -1262,18 +871,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "portable-atomic" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" - [[package]] name = "powerfmt" version = "0.2.0" @@ -1301,9 +898,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -1405,35 +1002,13 @@ dependencies = [ "getrandom 0.2.16", ] -[[package]] -name = "rcgen" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" -dependencies = [ - "pem", - "ring", - "rustls-pki-types", - "time", - "yasna", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags", ] [[package]] @@ -1479,19 +1054,13 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - [[package]] name = "rustix" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.4", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1500,9 +1069,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.32" +version = "0.23.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" dependencies = [ "log", "once_cell", @@ -1524,18 +1093,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.7" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "ring", "rustls-pki-types", @@ -1584,26 +1153,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "shlex" version = "1.3.0" @@ -1671,9 +1220,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -1693,7 +1242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2", @@ -1708,26 +1257,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "time" version = "0.3.44" @@ -1763,29 +1292,26 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", "socket2 0.6.1", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -1813,21 +1339,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-tar" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" -dependencies = [ - "filetime", - "futures-core", - "libc", - "redox_syscall 0.3.5", - "tokio", - "tokio-stream", - "xattr", -] - [[package]] name = "tokio-util" version = "0.7.16" @@ -1841,47 +1352,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap 2.11.4", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "tonic" version = "0.12.3" @@ -2025,17 +1495,11 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "untrusted" @@ -2049,12 +1513,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "vergen" version = "8.3.2" @@ -2067,12 +1525,6 @@ dependencies = [ "time", ] -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "want" version = "0.3.1" @@ -2088,15 +1540,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -2152,15 +1595,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" @@ -2308,40 +1742,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" -[[package]] -name = "winnow" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" -dependencies = [ - "memchr", -] - [[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" -[[package]] -name = "xattr" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" -dependencies = [ - "libc", - "rustix", -] - -[[package]] -name = "yasna" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" -dependencies = [ - "time", -] - [[package]] name = "zerocopy" version = "0.8.27" diff --git a/examples/client_usage/Cargo.toml b/examples/client_usage/Cargo.toml index ec34d894..17d34b4a 100644 --- a/examples/client_usage/Cargo.toml +++ b/examples/client_usage/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "dengine-client-demo" -version = "0.1.0" +version = "0.2.0" edition = "2021" [dependencies] -d-engine = { path = "../../../d-engine" } +d-engine-client = { path = "../../d-engine-client" } clap = { version = "4.0", features = ["derive"] } tokio = { version = "1.36.0", features = ["full"] } diff --git a/examples/client_usage/src/main.rs b/examples/client_usage/src/main.rs index 398cd230..a8d8c87d 100644 --- a/examples/client_usage/src/main.rs +++ b/examples/client_usage/src/main.rs @@ -1,13 +1,11 @@ use anyhow::Result; use clap::Parser; use clap::Subcommand; -use d_engine::client::ClientApiError; -use d_engine::config::ReadConsistencyPolicy; -use d_engine::proto::client::ClientResult; -use d_engine::proto::cluster::NodeMeta; -use d_engine::proto::common::NodeStatus; -use d_engine::ClientBuilder; -use d_engine::ConvertError; +use d_engine_client::{ + cluster_types::{NodeMeta, NodeStatus}, + protocol::{ClientResult, ReadConsistencyPolicy}, + Client, ClientApiError, ClientBuilder, +}; #[derive(Parser)] #[command(version, about, long_about = None)] @@ -73,7 +71,7 @@ async fn main() -> Result<()> { } async fn handle_write( - client: &d_engine::Client, + client: &Client, key: u64, value: u64, ) -> Result<()> { @@ -87,7 +85,7 @@ async fn handle_write( } async fn handle_delete( - client: &d_engine::Client, + client: &Client, key: u64, ) -> Result<()> { println!("Deleting key({key})"); @@ -100,7 +98,7 @@ async fn handle_delete( } async fn handle_read( - client: &d_engine::Client, + client: &Client, key: u64, consistency: &str, ) -> Result<()> { @@ -129,7 +127,7 @@ async fn handle_read( } async fn handle_cluster_command( - client: &d_engine::Client, + client: &Client, node_id: u32, address: String, ) -> crate::Result<()> { @@ -160,7 +158,11 @@ pub fn safe_vk>(bytes: K) -> crate::Result { let expected_len = 8; if bytes.len() != expected_len { - return Err(ConvertError::InvalidLength(bytes.len()).into()); + return Err(anyhow::anyhow!( + "Invalid length for value key conversion: expected {}, got {}", + expected_len, + bytes.len() + )); } let array: [u8; 8] = bytes.try_into().expect("Guaranteed safe after length check"); Ok(u64::from_be_bytes(array)) @@ -170,7 +172,7 @@ pub fn safe_vk>(bytes: K) -> crate::Result { /// /// # Examples /// ``` -/// use d_engine::convert::safe_kv; +/// use convert::safe_kv; /// /// let bytes = safe_kv(0x1234_5678_9ABC_DEF0); /// assert_eq!(bytes, [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]); diff --git a/examples/rocksdb-cluster/Cargo.lock b/examples/rocksdb-cluster/Cargo.lock index c9c77f3e..07d8653a 100644 --- a/examples/rocksdb-cluster/Cargo.lock +++ b/examples/rocksdb-cluster/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" @@ -31,9 +22,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -46,9 +37,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arc-swap" @@ -58,15 +49,13 @@ checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "async-compression" -version = "0.4.28" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6448dfb3960f0b038e88c781ead1e7eb7929dfc3a71a1336ec9086c00f6d1e75" +checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" dependencies = [ "compression-codecs", "compression-core", - "flate2", "futures-core", - "memchr", "pin-project-lite", "tokio", ] @@ -118,9 +107,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.13.3" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" dependencies = [ "aws-lc-sys", "zeroize", @@ -128,11 +117,11 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.30.0" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" +checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" dependencies = [ - "bindgen 0.69.5", + "bindgen", "cc", "cmake", "dunce", @@ -186,21 +175,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base64" version = "0.22.1" @@ -236,43 +210,22 @@ dependencies = [ "virtue", ] -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "bitflags 2.9.3", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn", - "which", -] - [[package]] name = "bindgen" version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.10.0", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.13.0", + "log", + "prettyplease", "proc-macro2", "quote", "regex", - "rustc-hash 2.1.1", + "rustc-hash", "shlex", "syn", ] @@ -285,9 +238,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.3" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" @@ -325,10 +278,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.34" +version = "1.2.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -345,9 +299,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clang-sys" @@ -371,22 +325,20 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.28" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46cc6539bf1c592cff488b9f253b30bc0ec50d15407c2cf45e27bd8f308d5905" +checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" dependencies = [ "compression-core", "flate2", - "futures-core", "memchr", - "pin-project-lite", ] [[package]] name = "compression-core" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2957e823c15bde7ecf1e8b64e537aa03a6be5fda0e2334e99887669e75b12e01" +checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" [[package]] name = "config" @@ -604,9 +556,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", ] @@ -641,12 +593,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -667,6 +619,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + [[package]] name = "fixedbitset" version = "0.5.7" @@ -675,9 +633,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -792,9 +750,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -808,27 +766,21 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" version = "0.3.3" @@ -847,7 +799,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.11.0", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -878,19 +830,16 @@ dependencies = [ ] [[package]] -name = "heck" -version = "0.5.0" +name = "hashbrown" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] -name = "home" -version = "0.5.11" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "http" @@ -993,9 +942,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "bytes", "futures-channel", @@ -1006,7 +955,7 @@ dependencies = [ "hyper", "libc", "pin-project-lite", - "socket2 0.6.0", + "socket2 0.6.1", "tokio", "tower-service", "tracing", @@ -1024,23 +973,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.15.5", -] - -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.3", - "cfg-if", - "libc", + "hashbrown 0.16.0", ] [[package]] @@ -1051,9 +989,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -1079,15 +1017,15 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1099,37 +1037,31 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" -version = "0.2.175" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-link", ] [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.10.0", "libc", - "redox_syscall 0.5.17", + "redox_syscall 0.5.18", ] [[package]] @@ -1138,7 +1070,7 @@ version = "0.17.3+10.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cef2a00ee60fe526157c9023edab23943fae1ce2ab6f4abb2a807c1746835de9" dependencies = [ - "bindgen 0.72.1", + "bindgen", "bzip2-sys", "cc", "libc", @@ -1160,31 +1092,24 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lru" @@ -1207,11 +1132,11 @@ dependencies = [ [[package]] name = "matchers" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] @@ -1222,15 +1147,15 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] @@ -1256,12 +1181,12 @@ dependencies = [ "hyper", "hyper-rustls", "hyper-util", - "indexmap 2.11.0", + "indexmap 2.12.0", "ipnet", "metrics", "metrics-util", "quanta", - "thiserror 2.0.16", + "thiserror 2.0.17", "tokio", "tracing", ] @@ -1301,17 +1226,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -1341,12 +1267,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "overload", - "winapi", + "windows-sys 0.61.2", ] [[package]] @@ -1364,15 +1289,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -1385,17 +1301,11 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1403,15 +1313,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.17", + "redox_syscall 0.5.18", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -1422,12 +1332,12 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "pem" -version = "3.0.5" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ "base64", - "serde", + "serde_core", ] [[package]] @@ -1443,7 +1353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.11.0", + "indexmap 2.12.0", ] [[package]] @@ -1517,9 +1427,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -1586,16 +1496,16 @@ dependencies = [ "libc", "once_cell", "raw-cpuid", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "web-sys", "winapi", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -1662,7 +1572,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -1676,11 +1586,11 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.5.0" +version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.10.0", ] [[package]] @@ -1707,56 +1617,41 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.10.0", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata", + "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "ring" @@ -1784,7 +1679,7 @@ dependencies = [ [[package]] name = "rocksdb-demo" -version = "0.1.3" +version = "0.2.0" dependencies = [ "async-trait", "bincode 2.0.1", @@ -1798,18 +1693,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -1818,35 +1701,22 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.9.3", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.31" +version = "0.23.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" dependencies = [ "aws-lc-rs", "log", @@ -1860,9 +1730,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -1881,18 +1751,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "aws-lc-rs", "ring", @@ -1914,11 +1784,11 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1929,11 +1799,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "3.3.0" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags 2.9.3", + "bitflags 2.10.0", "core-foundation", "core-foundation-sys", "libc", @@ -1942,9 +1812,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -1952,18 +1822,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1972,14 +1852,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -2026,6 +1907,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "sketches-ddsketch" version = "0.3.0" @@ -2056,12 +1943,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2072,9 +1959,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -2089,15 +1976,15 @@ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] name = "tempfile" -version = "3.21.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", - "rustix 1.0.8", - "windows-sys 0.60.2", + "rustix", + "windows-sys 0.61.2", ] [[package]] @@ -2111,11 +1998,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.17", ] [[package]] @@ -2131,9 +2018,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -2151,9 +2038,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -2168,15 +2055,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -2184,28 +2071,25 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "pin-project-lite", "signal-hook-registry", - "slab", - "socket2 0.6.0", + "socket2 0.6.1", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -2214,9 +2098,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", @@ -2288,7 +2172,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.11.0", + "indexmap 2.12.0", "serde", "serde_spanned", "toml_datetime", @@ -2475,14 +2359,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex", + "regex-automata", "serde", "serde_json", "sharded-slab", @@ -2502,15 +2386,15 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "untrusted" @@ -2576,44 +2460,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2621,48 +2493,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.44", -] - [[package]] name = "winapi" version = "0.3.9" @@ -2687,9 +2547,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" @@ -2702,20 +2562,20 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.60.2" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets 0.53.3", + "windows-link", ] [[package]] @@ -2736,19 +2596,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -2759,9 +2619,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -2771,9 +2631,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -2783,9 +2643,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -2795,9 +2655,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -2807,9 +2667,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -2819,9 +2679,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -2831,9 +2691,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -2843,9 +2703,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" @@ -2857,22 +2717,19 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.3", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "xattr" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.0.8", + "rustix", ] [[package]] @@ -2886,18 +2743,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -2906,15 +2763,15 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/examples/rocksdb-cluster/Cargo.toml b/examples/rocksdb-cluster/Cargo.toml index 6b03b532..cf6d04f7 100644 --- a/examples/rocksdb-cluster/Cargo.toml +++ b/examples/rocksdb-cluster/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rocksdb-demo" -version = "0.1.3" -edition = "2021" +version = "0.2.0" +edition = "2024" [dependencies] # d-engine = "0.1.4" diff --git a/examples/rocksdb-cluster/src/main.rs b/examples/rocksdb-cluster/src/main.rs index 23b5e590..cbc13ef2 100644 --- a/examples/rocksdb-cluster/src/main.rs +++ b/examples/rocksdb-cluster/src/main.rs @@ -1,13 +1,13 @@ -use d_engine_server::file_io::open_file_for_append; use d_engine_server::NodeBuilder; use d_engine_server::{RocksDBStateMachine, RocksDBStorageEngine}; use std::env; use std::error::Error; +use std::fs::OpenOptions; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; -use tokio::signal::unix::signal; use tokio::signal::unix::SignalKind; +use tokio::signal::unix::signal; use tokio::sync::watch; use tracing::{error, info}; use tracing_appender::non_blocking::WorkerGuard; @@ -125,3 +125,16 @@ async fn graceful_shutdown(graceful_tx: watch::Sender<()>) { info!("Shutdown completed"); } + +fn open_file_for_append(path: PathBuf) -> Result> { + // Create parent directories if they don't exist + if let Some(parent) = path.parent() { + if parent != Path::new("") { + std::fs::create_dir_all(parent)?; + } + } + + let log_file = OpenOptions::new().append(true).create(true).open(&path)?; + + Ok(log_file) +} diff --git a/examples/sled-cluster/Cargo.toml b/examples/sled-cluster/Cargo.toml index f96f1944..8d78a00c 100644 --- a/examples/sled-cluster/Cargo.toml +++ b/examples/sled-cluster/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" # d-engine = "0.1.4" d-engine-server = { path = "../../d-engine-server" } - sled = { version = "0.34.7", features = [ "compression", ], default-features = false } diff --git a/examples/sled-cluster/src/converters.rs b/examples/sled-cluster/src/converters.rs new file mode 100644 index 00000000..ca8864fd --- /dev/null +++ b/examples/sled-cluster/src/converters.rs @@ -0,0 +1,47 @@ +//! Helper functions for converting between u64 and byte arrays. +//! +//! This module provides utility functions for serializing/deserializing +//! u64 values to/from byte arrays used as keys in sled. + +use d_engine_server::StorageError; +use sled::IVec; + +/// Convert a u64 value to an 8-byte big-endian vector. +/// +/// # Arguments +/// * `value` - The u64 value to convert +/// +/// # Returns +/// An 8-byte vector in big-endian byte order +#[inline] +pub(crate) fn safe_kv(value: u64) -> Vec { + value.to_be_bytes().to_vec() +} + +/// Convert an 8-byte big-endian slice to a u64 value. +/// +/// # Arguments +/// * `bytes` - A byte slice to convert +/// +/// # Returns +/// `Ok(u64)` if the slice is exactly 8 bytes, `Err` otherwise +#[inline] +pub(crate) fn safe_vk(bytes: &[u8]) -> Result { + if bytes.len() != 8 { + return Err(StorageError::DbError(format!( + "Invalid byte length: expected 8, got {}", + bytes.len() + ))); + } + let mut arr = [0u8; 8]; + arr.copy_from_slice(bytes); + Ok(u64::from_be_bytes(arr)) +} + +/// Convert an IVec (sled's byte vector) to a u64 value. +/// +/// This is a convenience wrapper around `safe_vk` for sled's IVec type. +#[inline] +pub(crate) fn safe_vk_ivec(ivec: IVec) -> Result { + safe_vk(&ivec[..]) +} diff --git a/examples/sled-cluster/src/lib.rs b/examples/sled-cluster/src/lib.rs index b55f4174..c6e7d5fd 100644 --- a/examples/sled-cluster/src/lib.rs +++ b/examples/sled-cluster/src/lib.rs @@ -1,5 +1,7 @@ +mod converters; mod sled_state_machine; mod sled_storage_engine; +pub(crate) use converters::{safe_kv, safe_vk, safe_vk_ivec}; pub use sled_state_machine::*; pub use sled_storage_engine::*; diff --git a/examples/sled-cluster/src/main.rs b/examples/sled-cluster/src/main.rs index 630333a8..a8c8eb89 100644 --- a/examples/sled-cluster/src/main.rs +++ b/examples/sled-cluster/src/main.rs @@ -2,6 +2,7 @@ use d_engine_server::NodeBuilder; use sled_demo::{SledStateMachine, SledStorageEngine}; use std::env; use std::error::Error; +use std::fs::{File, OpenOptions}; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; @@ -89,6 +90,21 @@ async fn start_dengine_server( println!("Exiting program."); } +/// Opens a file for appending, creating parent directories if needed +fn open_file_for_append>(path: P) -> std::io::Result { + let path = path.as_ref(); + + // Create parent directories if they don't exist + if let Some(parent) = path.parent() { + if !parent.as_os_str().is_empty() { + std::fs::create_dir_all(parent)?; + } + } + + // Open file for appending, create if doesn't exist + OpenOptions::new().create(true).append(true).open(path) +} + pub fn init_observability(log_dir: String) -> Result> { let log_file = open_file_for_append(Path::new(&log_dir).join("d.log")).unwrap(); diff --git a/examples/sled-cluster/src/sled_engine_test.rs b/examples/sled-cluster/src/sled_engine_test.rs index f90fc616..128d37cf 100644 --- a/examples/sled-cluster/src/sled_engine_test.rs +++ b/examples/sled-cluster/src/sled_engine_test.rs @@ -1,7 +1,13 @@ use super::*; -use async_trait::async_trait; use bytes::{Bytes, BytesMut}; -use d_engine_server::StorageEngine; +use d_engine_server::{ + client::{ + write_command::{Insert, Operation}, + WriteCommand, + }, + common::{entry_payload::Payload, Entry, EntryPayload}, + LogStore, Result, StorageEngine, +}; use prost::Message; use std::sync::Arc; use tempfile::TempDir; @@ -9,45 +15,60 @@ use tracing::debug; use tracing_test::traced_test; use uuid::Uuid; +/// Helper for building temporary sled storage engines for testing pub struct SledStorageEngineBuilder { temp_dir: TempDir, } impl SledStorageEngineBuilder { + /// Creates a new builder with a temporary directory pub fn new() -> Self { let temp_dir = TempDir::new().unwrap(); Self { temp_dir } } -} - -#[async_trait] -impl StorageEngineBuilder for SledStorageEngineBuilder { - type Engine = SledStorageEngine; - async fn build(&self) -> Result, Error> { + /// Builds a new storage engine instance + pub async fn build(&self) -> Result> { let unique_path = format!("sled_{}", Uuid::new_v4()); let path = self.temp_dir.path().join(unique_path); - let engine = SledStorageEngine::new(path, 1)?; Ok(Arc::new(engine)) } - - async fn cleanup(&self) -> Result<(), Error> { - // TempDir will be cleaned up automatically when dropped - Ok(()) - } } #[tokio::test] #[traced_test] -async fn test_sled_storage_engine() -> Result<(), Error> { +async fn test_sled_storage_engine_basic() -> Result<()> { let builder = SledStorageEngineBuilder::new(); - StorageEngineTestSuite::run_all_tests(builder).await + let engine = builder.build().await?; + let log_store = engine.log_store(); + + // Basic test: persist a few entries + let entries = vec![ + Entry { + index: 1, + term: 1, + payload: Some(create_test_command_payload(1)), + }, + Entry { + index: 2, + term: 1, + payload: Some(create_test_command_payload(2)), + }, + ]; + + log_store.persist_entries(entries).await?; + + // Verify entries can be retrieved + let retrieved = log_store.entry(1).await?; + assert!(retrieved.is_some(), "Entry 1 should exist"); + + Ok(()) } #[tokio::test] #[traced_test] -async fn test_sled_performance() -> Result<(), Error> { +async fn test_sled_performance() -> Result<()> { let builder = SledStorageEngineBuilder::new(); let engine = builder.build().await?; let log_store = engine.log_store(); @@ -57,7 +78,7 @@ async fn test_sled_performance() -> Result<(), Error> { let entries = (1..=10000) .map(|i| Entry { index: i, - term: i, + term: 1, payload: Some(create_test_command_payload(i)), }) .collect(); @@ -67,29 +88,29 @@ async fn test_sled_performance() -> Result<(), Error> { debug!("Persisted 10,000 entries in {duration:?}"); assert!( - duration.as_millis() < 1000, - "Should persist 10k entries in <1s" + duration.as_millis() < 5000, + "Should persist 10k entries in <5s" ); - builder.cleanup().await?; Ok(()) } -fn create_test_command_payload(index: u64) -> common::EntryPayload { - // Create a simple insert command +/// Helper function to create a test command payload +fn create_test_command_payload(index: u64) -> EntryPayload { let key = Bytes::from(format!("key_{index}").into_bytes()); let value = Bytes::from(format!("value_{index}").into_bytes()); let insert = Insert { key, value }; - let operation = client::write_command::Operation::Insert(insert); - let write_cmd = client::WriteCommand { + let operation = Operation::Insert(insert); + let write_cmd = WriteCommand { operation: Some(operation), }; let mut buffer = BytesMut::new(); write_cmd.encode(&mut buffer).expect("Failed to encode insert command"); let buffer = buffer.freeze(); - common::EntryPayload { - payload: Some(common::entry_payload::Payload::Command(buffer)), + + EntryPayload { + payload: Some(Payload::Command(buffer)), } } diff --git a/examples/sled-cluster/src/sled_state_machine.rs b/examples/sled-cluster/src/sled_state_machine.rs index 5c67e3ff..0302659b 100644 --- a/examples/sled-cluster/src/sled_state_machine.rs +++ b/examples/sled-cluster/src/sled_state_machine.rs @@ -4,20 +4,13 @@ use arc_swap::ArcSwap; use async_trait::async_trait; use bincode::config; use bytes::Bytes; -use d_engine_server::client::write_command::Delete; -use d_engine_server::client::write_command::Insert; -use d_engine_server::client::write_command::Operation; -use d_engine_server::client::WriteCommand; -use d_engine_server::common::entry_payload::Payload; -use d_engine_server::convert::safe_kv; -use d_engine_server::convert::safe_vk; -use d_engine_server::server::storage::SnapshotMetadata; -use d_engine_server::Entry; -use d_engine_server::Error; -use d_engine_server::LogId; -use d_engine_server::SnapshotError; -use d_engine_server::StateMachine; -use d_engine_server::StorageError; +use d_engine_server::{ + client::write_command::{Delete, Insert, Operation}, + client::WriteCommand, + common::{entry_payload::Payload, Entry, LogId}, + server_storage::SnapshotMetadata, + Result, SnapshotError, StateMachine, StorageError, +}; use parking_lot::Mutex; use prost::Message; use sled::Batch; @@ -37,6 +30,7 @@ use tracing::trace; use tracing::warn; use crate::compute_checksum_from_folder_path; +use crate::{safe_kv, safe_vk, safe_vk_ivec}; /// Sled database tree namespaces pub(crate) const STATE_MACHINE_TREE: &str = "_state_machine_tree"; @@ -97,13 +91,13 @@ impl Drop for SledStateMachine { #[async_trait] impl StateMachine for SledStateMachine { - fn start(&self) -> Result<(), Error> { + fn start(&self) -> Result<()> { debug!("start state machine"); self.is_serving.store(true, Ordering::Release); Ok(()) } - fn stop(&self) -> Result<(), Error> { + fn stop(&self) -> Result<()> { debug!("stop state machine"); self.is_serving.store(false, Ordering::Release); Ok(()) @@ -125,12 +119,15 @@ impl StateMachine for SledStateMachine { fn update_last_snapshot_metadata( &self, snapshot_metadata: &SnapshotMetadata, - ) -> Result<(), Error> { + ) -> Result<()> { debug!(%self.node_id, ?snapshot_metadata, "update_last_snapshot_metadata"); let last_included = snapshot_metadata.last_included.unwrap(); self.last_included_index.store(last_included.index, Ordering::SeqCst); self.last_included_term.store(last_included.term, Ordering::SeqCst); - *self.last_snapshot_checksum.lock() = Some(snapshot_metadata.checksum_array()?); + let checksum = snapshot_metadata + .checksum_array() + .map_err(|e| SnapshotError::OperationFailed(format!("{e:?}")))?; + *self.last_snapshot_checksum.lock() = Some(checksum); Ok(()) } @@ -164,7 +161,7 @@ impl StateMachine for SledStateMachine { fn get( &self, key_buffer: &[u8], - ) -> Result, Error> { + ) -> Result> { match self.current_tree().get(key_buffer) { Ok(Some(v)) => Ok(Some(Bytes::copy_from_slice(&v))), Ok(None) => Ok(None), @@ -192,7 +189,7 @@ impl StateMachine for SledStateMachine { async fn apply_chunk( &self, chunk: Vec, - ) -> Result<(), Error> { + ) -> Result<()> { trace!("Applying chunk: {:?}.", chunk); let mut highest_index_entry: Option = None; @@ -280,7 +277,7 @@ impl StateMachine for SledStateMachine { Ok(()) } - fn save_hard_state(&self) -> Result<(), Error> { + fn save_hard_state(&self) -> Result<()> { let last_applied = self.last_applied(); self.persist_last_applied(last_applied)?; @@ -295,7 +292,7 @@ impl StateMachine for SledStateMachine { fn persist_last_applied( &self, last_applied: LogId, - ) -> Result<(), Error> { + ) -> Result<()> { debug!(%self.node_id, ?last_applied, "persist_last_applied"); let db = self.db.load(); let tree = db @@ -303,12 +300,12 @@ impl StateMachine for SledStateMachine { .map_err(|e| StorageError::DbError(e.to_string()))?; tree.insert( STATE_MACHINE_META_KEY_LAST_APPLIED_INDEX, - &safe_kv(last_applied.index), + safe_kv(last_applied.index), ) .map_err(|e| StorageError::DbError(e.to_string()))?; tree.insert( STATE_MACHINE_META_KEY_LAST_APPLIED_TERM, - &safe_kv(last_applied.term), + safe_kv(last_applied.term), ) .map_err(|e| StorageError::DbError(e.to_string()))?; @@ -319,7 +316,7 @@ impl StateMachine for SledStateMachine { fn persist_last_snapshot_metadata( &self, last_snapshot_metadata: &SnapshotMetadata, - ) -> Result<(), Error> { + ) -> Result<()> { debug!(%self.node_id, ?last_snapshot_metadata, "persist_last_snapshot_metadata"); let db = self.db.load(); let tree = db @@ -330,7 +327,7 @@ impl StateMachine for SledStateMachine { Ok(()) } - fn flush(&self) -> Result<(), Error> { + fn flush(&self) -> Result<()> { let db = self.db.load(); match db.flush() { Ok(bytes) => { @@ -348,7 +345,7 @@ impl StateMachine for SledStateMachine { Ok(()) } - async fn flush_async(&self) -> Result<(), Error> { + async fn flush_async(&self) -> Result<()> { self.flush() } @@ -357,7 +354,7 @@ impl StateMachine for SledStateMachine { &self, new_snapshot_dir: PathBuf, last_included: LogId, - ) -> Result { + ) -> Result { // 1. Get a lightweight write lock (to prevent concurrent snapshot generation) let _guard = self.snapshot_lock.write().await; @@ -379,7 +376,7 @@ impl StateMachine for SledStateMachine { Ok(v) => v, Err(e) => { error!(?e, "generate_snapshot_data::safe_vk"); - return Err(e); + return Err(e.into()); } }; if key_num > last_included.index { @@ -437,7 +434,7 @@ impl StateMachine for SledStateMachine { &self, metadata: &SnapshotMetadata, decompressed_snapshot_path: PathBuf, - ) -> Result<(), Error> { + ) -> Result<()> { if let Some(new_last_included) = metadata.last_included { debug!( ?new_last_included, @@ -521,7 +518,7 @@ impl StateMachine for SledStateMachine { self.current_tree().len() } - async fn reset(&self) -> Result<(), Error> { + async fn reset(&self) -> Result<()> { let db = self.db.load(); db.clear().map_err(|e| StorageError::DbError(e.to_string()))?; Ok(()) @@ -532,7 +529,7 @@ impl SledStateMachine { pub fn new + std::fmt::Debug>( path: P, node_id: u32, - ) -> Result { + ) -> Result { let db = Arc::new( init_sled_state_machine_db(path).map_err(|e| StorageError::DbError(e.to_string()))?, ); @@ -582,25 +579,23 @@ impl SledStateMachine { Ok(sm) } - fn load_state_machine_metadata(tree: &sled::Tree) -> Result<(u64, u64), Error> { + fn load_state_machine_metadata(tree: &sled::Tree) -> Result<(u64, u64)> { let index = tree .get(STATE_MACHINE_META_KEY_LAST_APPLIED_INDEX) .map_err(|e| StorageError::DbError(e.to_string()))? - .map(safe_vk) + .map(safe_vk_ivec) .unwrap_or(Ok(0))?; let term = tree .get(STATE_MACHINE_META_KEY_LAST_APPLIED_TERM) .map_err(|e| StorageError::DbError(e.to_string()))? - .map(safe_vk) + .map(safe_vk_ivec) .unwrap_or(Ok(0))?; Ok((index, term)) } - pub(super) fn load_snapshot_metadata( - tree: &sled::Tree - ) -> Result, Error> { + pub(super) fn load_snapshot_metadata(tree: &sled::Tree) -> Result> { if let Ok(Some(v)) = tree.get(LAST_SNAPSHOT_METADATA_KEY) { info!( "found SnapshotMetadata from DB with key: {}", @@ -631,7 +626,7 @@ impl SledStateMachine { pub(super) fn apply_batch( &self, batch: Batch, - ) -> Result<(), Error> { + ) -> Result<()> { if let Err(e) = self.current_tree().apply_batch(batch) { error!("state_machine apply_batch failed: {}", e); return Err(StorageError::DbError(e.to_string()).into()); @@ -650,7 +645,7 @@ impl SledStateMachine { &self, tree: sled::Tree, last_snapshot_metadata: &SnapshotMetadata, - ) -> Result<(), Error> { + ) -> Result<()> { let config = config::standard(); let v = bincode::serde::encode_to_vec(last_snapshot_metadata, config) .map_err(|e| StorageError::DbError(e.to_string()))?; @@ -668,14 +663,14 @@ impl SledStateMachine { fn new_tree( db: &sled::Db, key: &str, -) -> Result { +) -> Result { db.open_tree(key) .map_err(|e| SnapshotError::OperationFailed(format!("{e:?}")).into()) } pub fn init_sled_state_machine_db( sled_db_root_path: impl AsRef + std::fmt::Debug -) -> Result { +) -> std::result::Result { tracing::debug!( "init_sled_state_machine_db from path: {:?}", sled_db_root_path diff --git a/examples/sled-cluster/src/sled_storage_engine.rs b/examples/sled-cluster/src/sled_storage_engine.rs index 833cd22b..a86341af 100644 --- a/examples/sled-cluster/src/sled_storage_engine.rs +++ b/examples/sled-cluster/src/sled_storage_engine.rs @@ -1,16 +1,10 @@ use async_trait::async_trait; use bincode::config; use bytes::Bytes; -use d_engine_server::common::Entry; -use d_engine_server::common::LogId; -use d_engine_server::convert::safe_vk; -use d_engine_server::Error; -use d_engine_server::HardState; -use d_engine_server::LogStore; -use d_engine_server::MetaStore; -use d_engine_server::ProstError; -use d_engine_server::StorageEngine; -use d_engine_server::StorageError; +use d_engine_server::{ + common::{Entry, LogId}, + Error, HardState, LogStore, MetaStore, ProstError, Result, StorageEngine, StorageError, +}; use prost::Message; use sled::Batch; use sled::IVec; @@ -62,7 +56,7 @@ impl LogStore for SledLogStore { async fn persist_entries( &self, entries: Vec, - ) -> Result<(), Error> { + ) -> Result<()> { let mut batch = Batch::default(); let mut max_index = 0; @@ -84,7 +78,7 @@ impl LogStore for SledLogStore { async fn entry( &self, index: u64, - ) -> Result, Error> { + ) -> Result> { let key = SledStorageEngine::index_to_key(index); match self.tree.get(key).map_err(|e| StorageError::DbError(e.to_string()))? { Some(bytes) => { @@ -99,7 +93,7 @@ impl LogStore for SledLogStore { fn get_entries( &self, range: RangeInclusive, - ) -> Result, Error> { + ) -> Result> { let start = SledStorageEngine::index_to_key(*range.start()); let end = SledStorageEngine::index_to_key(*range.end()); let mut entries = Vec::new(); @@ -117,7 +111,7 @@ impl LogStore for SledLogStore { async fn purge( &self, cutoff_index: LogId, - ) -> Result<(), Error> { + ) -> Result<()> { let start = SledStorageEngine::index_to_key(0); let end = SledStorageEngine::index_to_key(cutoff_index.index); let mut batch = Batch::default(); @@ -135,7 +129,7 @@ impl LogStore for SledLogStore { async fn truncate( &self, from_index: u64, - ) -> Result<(), Error> { + ) -> Result<()> { let start_key = SledStorageEngine::index_to_key(from_index); let mut batch = Batch::default(); @@ -149,13 +143,13 @@ impl LogStore for SledLogStore { } #[instrument(skip(self))] - fn flush(&self) -> Result<(), Error> { + fn flush(&self) -> Result<()> { trace!("LogStore flush"); self.tree.flush().map_err(|e| StorageError::DbError(e.to_string()))?; Ok(()) } - async fn flush_async(&self) -> Result<(), Error> { + async fn flush_async(&self) -> Result<()> { trace!("LogStore flush"); self.tree .flush_async() @@ -165,7 +159,7 @@ impl LogStore for SledLogStore { } #[instrument(skip(self))] - async fn reset(&self) -> Result<(), Error> { + async fn reset(&self) -> Result<()> { self.tree.clear().map_err(|e| StorageError::DbError(e.to_string()))?; Ok(()) } @@ -173,13 +167,16 @@ impl LogStore for SledLogStore { #[instrument(skip(self))] fn last_index(&self) -> u64 { match self.tree.last() { - Ok(Some((key, _))) => match safe_vk(&key) { - Ok(index) => index, - Err(err) => { - tracing::warn!("Invalid key format in sled: {:?}", err); + Ok(Some((key, _))) => { + if key.len() >= 8 { + let mut bytes = [0u8; 8]; + bytes.copy_from_slice(&key[0..8]); + u64::from_be_bytes(bytes) + } else { + tracing::warn!("Invalid key format in sled: key too short"); 0 } - }, + } _ => 0, } } @@ -190,7 +187,7 @@ impl MetaStore for SledMetaStore { fn save_hard_state( &self, state: &HardState, - ) -> Result<(), Error> { + ) -> Result<()> { let config = config::standard(); match bincode::serde::encode_to_vec(state, config) { Ok(v) => { @@ -219,7 +216,7 @@ impl MetaStore for SledMetaStore { Ok(()) } - fn load_hard_state(&self) -> Result, Error> { + fn load_hard_state(&self) -> Result> { info!( "pending load_role_hard_state_from_db with key: {:?}", HARD_STATE_KEY @@ -262,12 +259,12 @@ impl MetaStore for SledMetaStore { } } - fn flush(&self) -> Result<(), Error> { + fn flush(&self) -> Result<()> { self.tree.flush().map_err(|e| StorageError::DbError(e.to_string()))?; Ok(()) } - async fn flush_async(&self) -> Result<(), Error> { + async fn flush_async(&self) -> Result<()> { trace!("MetaStore flush"); self.tree .flush_async() @@ -307,7 +304,7 @@ impl SledStorageEngine { pub fn new + std::fmt::Debug>( path: P, node_id: u32, - ) -> Result { + ) -> Result { let (log_tree, meta_tree) = init_sled_log_tree_and_meta_tree(path, node_id) .map_err(|e| StorageError::DbError(e.to_string()))?; @@ -344,7 +341,7 @@ impl SledLogStore { &self, key: K, value: V, - ) -> Result>, Error> + ) -> Result>> where K: AsRef<[u8]> + 'static, V: AsRef<[u8]> + 'static, @@ -363,7 +360,7 @@ impl SledLogStore { pub fn get( &self, key: K, - ) -> Result, Error> + ) -> Result> where K: AsRef<[u8]> + Send + 'static, { @@ -390,7 +387,7 @@ impl SledMetaStore { &self, key: K, value: V, - ) -> Result>, Error> + ) -> Result>> where K: AsRef<[u8]> + 'static, V: AsRef<[u8]> + 'static, @@ -409,7 +406,7 @@ impl SledMetaStore { pub fn get( &self, key: K, - ) -> Result, Error> + ) -> Result> where K: AsRef<[u8]> + Send + 'static, { @@ -433,7 +430,7 @@ impl SledMetaStore { pub fn init_sled_log_tree_and_meta_tree( sled_db_root_path: impl AsRef + std::fmt::Debug, node_id: u32, -) -> Result<(sled::Tree, sled::Tree), sled::Error> { +) -> std::result::Result<(sled::Tree, sled::Tree), sled::Error> { let db = init_sled_storage_engine_db(&sled_db_root_path)?; let log_tree_name = format!("raft_log_{RAFT_LOG_NAMESPACE}_{node_id}"); let meta_tree_name = format!("raft_meta_{RAFT_META_NAMESPACE}_{node_id}"); @@ -445,7 +442,7 @@ pub fn init_sled_log_tree_and_meta_tree( pub fn init_sled_storage_engine_db( sled_db_root_path: impl AsRef + std::fmt::Debug -) -> Result { +) -> std::result::Result { tracing::debug!( "init_sled_storage_engine_db from path: {:?}", &sled_db_root_path From 4b45bcf08cba64133cb7786d599092845650fc6c Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Sat, 1 Nov 2025 22:10:19 +0800 Subject: [PATCH 22/26] feat #167: update Makefile --- Makefile | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index ad8b878b..2072aa41 100644 --- a/Makefile +++ b/Makefile @@ -297,17 +297,21 @@ bench: check-workspace # DOCUMENTATION # ============================================================================ -## docs Generate API documentation and open in browser +## docs Generate API documentation and open in browser (opens d-engine-docs hub) docs: check-workspace @echo "$(BLUE)Generating API documentation for all workspace crates...$(NC)" - @$(CARGO) doc --workspace --no-deps --open - @echo "$(GREEN)✓ Documentation generated and opened$(NC)" + @$(CARGO) doc --workspace --no-deps + @echo "$(GREEN)✓ Documentation generated$(NC)" + @echo "$(CYAN)Opening documentation hub at: target/doc/d_engine_docs/index.html$(NC)" + @open "file://$$(pwd)/target/doc/d_engine_docs/index.html" 2>/dev/null || xdg-open "file://$$(pwd)/target/doc/d_engine_docs/index.html" 2>/dev/null || echo "Please open: target/doc/d_engine_docs/index.html" ## docs-all Generate documentation with all features enabled docs-all: check-workspace @echo "$(BLUE)Generating API documentation (all features)...$(NC)" - @$(CARGO) doc --workspace --all-features --no-deps --open - @echo "$(GREEN)✓ Documentation generated and opened$(NC)" + @$(CARGO) doc --workspace --all-features --no-deps + @echo "$(GREEN)✓ Documentation generated$(NC)" + @echo "$(CYAN)Opening documentation hub at: target/doc/d_engine_docs/index.html$(NC)" + @open "file://$$(pwd)/target/doc/d_engine_docs/index.html" 2>/dev/null || xdg-open "file://$$(pwd)/target/doc/d_engine_docs/index.html" 2>/dev/null || echo "Please open: target/doc/d_engine_docs/index.html" ## docs-check Check documentation without opening browser (CI-safe) docs-check: check-workspace @@ -324,8 +328,10 @@ docs-check-all: check-workspace ## docs-private Generate documentation including private items (architecture) docs-private: check-workspace @echo "$(BLUE)Generating documentation with private items...$(NC)" - @$(CARGO) doc --workspace --no-deps --document-private-items --open - @echo "$(GREEN)✓ Documentation generated with private items$(NC)" + @$(CARGO) doc --workspace --no-deps --document-private-items + @echo "$(GREEN)✓ Documentation generated$(NC)" + @echo "$(CYAN)Opening documentation hub at: target/doc/d_engine_docs/index.html$(NC)" + @open "file://$$(pwd)/target/doc/d_engine_docs/index.html" 2>/dev/null || xdg-open "file://$$(pwd)/target/doc/d_engine_docs/index.html" 2>/dev/null || echo "Please open: target/doc/d_engine_docs/index.html" ## docs-crate Generate documentation for a single crate (usage: make docs-crate CRATE=d-engine-server) docs-crate: check-workspace @@ -334,8 +340,10 @@ ifndef CRATE @exit 1 endif @echo "$(BLUE)Generating documentation for crate: $(CRATE)...$(NC)" - @$(CARGO) doc -p $(CRATE) --no-deps --open - @echo "$(GREEN)✓ Documentation generated for $(CRATE)$(NC)" + @$(CARGO) doc -p $(CRATE) --no-deps + @echo "$(GREEN)✓ Documentation generated$(NC)" + @echo "$(CYAN)Opening crate documentation at: target/doc/$$(echo $(CRATE) | sed 's/-/_/g')/index.html$(NC)" + @open "file://$$(pwd)/target/doc/$$(echo $(CRATE) | sed 's/-/_/g')/index.html" 2>/dev/null || xdg-open "file://$$(pwd)/target/doc/$$(echo $(CRATE) | sed 's/-/_/g')/index.html" 2>/dev/null || echo "Please open: target/doc/$$(echo $(CRATE) | sed 's/-/_/g')/index.html" ## docs-clean Remove generated documentation artifacts docs-clean: From fe4bcd61f97b3d68eb8cfb835e971ae039a6d2d4 Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Sat, 1 Nov 2025 23:46:49 +0800 Subject: [PATCH 23/26] feat #167: add more unit tests --- .github/workflows/ci.yml | 2 +- d-engine-core/src/lib.rs | 3 + d-engine-core/src/maybe_clone_oneshot_test.rs | 227 ++++++++++++++++ d-engine-core/src/test_utils/common_test.rs | 242 ++++++++++++++++++ .../src/test_utils/entry_builder_test.rs | 214 ++++++++++++++++ d-engine-core/src/test_utils/mod.rs | 6 + 6 files changed, 693 insertions(+), 1 deletion(-) create mode 100644 d-engine-core/src/maybe_clone_oneshot_test.rs create mode 100644 d-engine-core/src/test_utils/common_test.rs create mode 100644 d-engine-core/src/test_utils/entry_builder_test.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d542ed8..3eb80397 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,7 @@ jobs: - name: Run tests with nextest and generate coverage run: | - cargo llvm-cov nextest --all-features --workspace --lcov --output-path lcov.info --ignore-filename-regex "src/generated/.*|src/errors.rs" + cargo llvm-cov nextest --all-features --workspace --lcov --output-path lcov.info --ignore-filename-regex "src/generated/.*|src/errors.rs|d-engine-core/src/event.rs" - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 diff --git a/d-engine-core/src/lib.rs b/d-engine-core/src/lib.rs index e242c685..b67a0029 100644 --- a/d-engine-core/src/lib.rs +++ b/d-engine-core/src/lib.rs @@ -43,6 +43,9 @@ pub(crate) use timer::*; #[doc(hidden)] pub use type_config::*; +#[cfg(test)] +mod maybe_clone_oneshot_test; + #[cfg(test)] mod raft_oneshot_test; diff --git a/d-engine-core/src/maybe_clone_oneshot_test.rs b/d-engine-core/src/maybe_clone_oneshot_test.rs new file mode 100644 index 00000000..75746360 --- /dev/null +++ b/d-engine-core/src/maybe_clone_oneshot_test.rs @@ -0,0 +1,227 @@ +use crate::maybe_clone_oneshot::{MaybeCloneOneshot, RaftOneshot}; +use std::sync::Arc; +use tokio::sync::Mutex; + +#[tokio::test] +async fn test_oneshot_basic_send_recv() { + let (tx, mut rx) = MaybeCloneOneshot::new(); + let value = 42; + + tx.send(value).expect("Failed to send"); + let received = rx.recv().await.expect("Failed to receive"); + + assert_eq!(received, value); +} + +#[tokio::test] +async fn test_oneshot_clone_sender() { + let (tx, mut rx) = MaybeCloneOneshot::new(); + let tx_cloned = tx.clone(); + + let value = 100; + tx_cloned.send(value).expect("Failed to send"); + let received = rx.recv().await.expect("Failed to receive"); + + assert_eq!(received, value); +} + +#[tokio::test] +async fn test_oneshot_clone_receiver() { + let (tx, rx) = MaybeCloneOneshot::new(); + let mut rx_cloned = rx.clone(); + + let value = 123; + tx.send(value).expect("Failed to send"); + let received = rx_cloned.recv().await.expect("Failed to receive"); + + assert_eq!(received, value); +} + +#[tokio::test] +async fn test_oneshot_multiple_clones() { + let (tx, rx1) = MaybeCloneOneshot::new(); + let rx2 = rx1.clone(); + let tx_cloned = tx.clone(); + + let value = 999; + tx_cloned.send(value).expect("Failed to send"); + + let mut rx1_mut = rx1; + let mut rx2_mut = rx2; + let received1 = rx1_mut.recv().await.expect("Failed to receive rx1"); + let received2 = rx2_mut.recv().await.expect("Failed to receive rx2"); + + assert_eq!(received1, value); + assert_eq!(received2, value); +} + +#[tokio::test] +async fn test_oneshot_with_string() { + let (tx, mut rx) = MaybeCloneOneshot::new(); + let value = "test_message".to_string(); + + tx.send(value.clone()).expect("Failed to send"); + let received = rx.recv().await.expect("Failed to receive"); + + assert_eq!(received, value); +} + +#[tokio::test] +async fn test_oneshot_concurrent_receivers() { + let (tx, rx) = MaybeCloneOneshot::new(); + let rx_clone1 = rx.clone(); + let rx_clone2 = rx.clone(); + + let value = 555; + tx.send(value).expect("Failed to send"); + + let handle1 = tokio::spawn(async move { + let mut r = rx_clone1; + r.recv().await.expect("Failed in task 1") + }); + + let handle2 = tokio::spawn(async move { + let mut r = rx_clone2; + r.recv().await.expect("Failed in task 2") + }); + + let result1 = handle1.await.expect("Task 1 failed"); + let result2 = handle2.await.expect("Task 2 failed"); + + assert_eq!(result1, value); + assert_eq!(result2, value); +} + +#[tokio::test] +async fn test_oneshot_with_vector() { + let (tx, mut rx) = MaybeCloneOneshot::new(); + let value = vec![1, 2, 3, 4, 5]; + + tx.send(value.clone()).expect("Failed to send"); + let received = rx.recv().await.expect("Failed to receive"); + + assert_eq!(received, value); +} + +#[tokio::test] +async fn test_oneshot_stress_multiple_sends() { + for i in 0..100 { + let (tx, mut rx) = MaybeCloneOneshot::new(); + tx.send(i).expect("Failed to send"); + let received = rx.recv().await.expect("Failed to receive"); + assert_eq!(received, i); + } +} + +#[tokio::test] +async fn test_oneshot_clone_chain() { + let (tx, rx) = MaybeCloneOneshot::new(); + let rx1 = rx.clone(); + let rx2 = rx1.clone(); + let mut rx3 = rx2.clone(); + + let value = 777; + tx.send(value).expect("Failed to send"); + + let received = rx3.recv().await.expect("Failed to receive"); + assert_eq!(received, value); +} + +#[tokio::test] +async fn test_oneshot_sender_clone_and_send() { + let (tx, mut rx) = MaybeCloneOneshot::new(); + let tx1 = tx.clone(); + let tx2 = tx1.clone(); + + let value = 444; + tx2.send(value).expect("Failed to send"); + let received = rx.recv().await.expect("Failed to receive"); + + assert_eq!(received, value); +} + +#[tokio::test] +async fn test_oneshot_with_custom_struct() { + #[derive(Debug, Clone, PartialEq)] + struct TestData { + id: u32, + name: String, + } + + let (tx, mut rx) = MaybeCloneOneshot::new(); + let value = TestData { + id: 42, + name: "test".to_string(), + }; + + tx.send(value.clone()).expect("Failed to send"); + let received = rx.recv().await.expect("Failed to receive"); + + assert_eq!(received, value); + assert_eq!(received.id, 42); + assert_eq!(received.name, "test"); +} + +#[tokio::test] +async fn test_oneshot_arc_shared_state() { + let (tx, mut rx) = MaybeCloneOneshot::new(); + let shared_data = Arc::new(Mutex::new(vec![1, 2, 3])); + let shared_clone = Arc::clone(&shared_data); + + tx.send(shared_clone).expect("Failed to send"); + let received_arc = rx.recv().await.expect("Failed to receive"); + let guard = received_arc.lock().await; + + assert_eq!(guard.len(), 3); + assert_eq!(guard[0], 1); + assert_eq!(guard[1], 2); + assert_eq!(guard[2], 3); +} + +#[tokio::test] +async fn test_oneshot_receiver_as_future() { + let (tx, rx) = MaybeCloneOneshot::new(); + let value = 333; + + tokio::spawn(async move { + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + tx.send(value).expect("Failed to send"); + }); + + let received = rx.await.expect("Failed to receive"); + assert_eq!(received, value); +} + +#[tokio::test] +async fn test_oneshot_with_zero() { + let (tx, mut rx) = MaybeCloneOneshot::new(); + let value = 0; + + tx.send(value).expect("Failed to send"); + let received = rx.recv().await.expect("Failed to receive"); + + assert_eq!(received, value); +} + +#[tokio::test] +async fn test_oneshot_with_max_u64() { + let (tx, mut rx) = MaybeCloneOneshot::new(); + let value = u64::MAX; + + tx.send(value).expect("Failed to send"); + let received = rx.recv().await.expect("Failed to receive"); + + assert_eq!(received, value); +} + +#[tokio::test] +async fn test_oneshot_empty_string() { + let (tx, mut rx) = MaybeCloneOneshot::new(); + let value = String::new(); + + tx.send(value.clone()).expect("Failed to send"); + let received = rx.recv().await.expect("Failed to receive"); + + assert_eq!(received, value); + assert!(received.is_empty()); +} diff --git a/d-engine-core/src/test_utils/common_test.rs b/d-engine-core/src/test_utils/common_test.rs new file mode 100644 index 00000000..a0e8677a --- /dev/null +++ b/d-engine-core/src/test_utils/common_test.rs @@ -0,0 +1,242 @@ + +use crate::test_utils::{ + create_config_entries, create_mixed_entries, generate_delete_commands, + generate_insert_commands, node_config, snapshot_config, +}; +use std::path::PathBuf; + +#[test] +fn test_create_mixed_entries_structure() { + let entries = create_mixed_entries(); + + assert_eq!(entries.len(), 2); + assert_eq!(entries[0].index, 1); + assert_eq!(entries[0].term, 1); + assert_eq!(entries[1].index, 2); + assert_eq!(entries[1].term, 1); +} + +#[test] +fn test_create_mixed_entries_config_entry() { + let entries = create_mixed_entries(); + let first_entry = &entries[0]; + + assert!(first_entry.payload.is_some()); +} + +#[test] +fn test_create_mixed_entries_app_entry() { + let entries = create_mixed_entries(); + let second_entry = &entries[1]; + + assert!(second_entry.payload.is_some()); + assert_eq!(second_entry.index, 2); +} + +#[test] +fn test_create_config_entries_count() { + let entries = create_config_entries(); + + assert_eq!(entries.len(), 1); +} + +#[test] +fn test_create_config_entries_values() { + let entries = create_config_entries(); + let entry = &entries[0]; + + assert_eq!(entry.index, 1); + assert_eq!(entry.term, 1); + assert!(entry.payload.is_some()); +} + +#[test] +fn test_generate_insert_commands_single() { + let commands = generate_insert_commands(vec![42]); + + assert!(!commands.is_empty()); +} + +#[test] +fn test_generate_insert_commands_multiple() { + let ids = vec![1, 2, 3, 4, 5]; + let commands = generate_insert_commands(ids); + + assert!(!commands.is_empty()); +} + +#[test] +fn test_generate_insert_commands_empty() { + let commands = generate_insert_commands(vec![]); + + assert!(commands.is_empty()); +} + +#[test] +fn test_generate_insert_commands_large_id() { + let commands = generate_insert_commands(vec![u64::MAX]); + + assert!(!commands.is_empty()); +} + +#[test] +fn test_generate_insert_commands_zero_id() { + let commands = generate_insert_commands(vec![0]); + + assert!(!commands.is_empty()); +} + +#[test] +fn test_generate_delete_commands_range() { + let commands = generate_delete_commands(1..=5); + + assert!(!commands.is_empty()); +} + +#[test] +fn test_generate_delete_commands_single_element() { + let commands = generate_delete_commands(1..=1); + + assert!(!commands.is_empty()); +} + +#[test] +fn test_generate_delete_commands_large_range() { + let commands = generate_delete_commands(1..=100); + + assert!(!commands.is_empty()); +} + +#[test] +fn test_snapshot_config_defaults() { + let dir = PathBuf::from("/tmp/snapshots"); + let config = snapshot_config(dir.clone()); + + assert_eq!(config.max_log_entries_before_snapshot, 1); + assert_eq!(config.cleanup_retain_count, 2); + assert_eq!(config.chunk_size, 1024); + assert!(config.enable); + assert_eq!(config.snapshots_dir, dir); +} + +#[test] +fn test_snapshot_config_paths() { + let dir = PathBuf::from("/var/raft/snapshots"); + let config = snapshot_config(dir.clone()); + + assert_eq!(config.snapshots_dir, dir); + assert_eq!(config.snapshots_dir_prefix, "snapshot-"); +} + +#[test] +fn test_snapshot_config_timeout_values() { + let dir = PathBuf::from("/tmp"); + let config = snapshot_config(dir); + + assert_eq!(config.transfer_timeout_in_sec, 1); + assert_eq!(config.retry_interval_in_ms, 1); + assert_eq!(config.push_timeout_in_ms, 100); +} + +#[test] +fn test_snapshot_config_bandwidth() { + let dir = PathBuf::from("/tmp"); + let config = snapshot_config(dir); + + assert_eq!(config.max_bandwidth_mbps, 1); + assert_eq!(config.max_retries, 1); +} + +#[test] +fn test_snapshot_config_queue_settings() { + let dir = PathBuf::from("/tmp"); + let config = snapshot_config(dir); + + assert_eq!(config.push_queue_size, 1); + assert_eq!(config.cache_size, 1); +} + +#[test] +fn test_snapshot_config_yield_settings() { + let dir = PathBuf::from("/tmp"); + let config = snapshot_config(dir); + + assert_eq!(config.sender_yield_every_n_chunks, 1); + assert_eq!(config.receiver_yield_every_n_chunks, 1); +} + +#[test] +fn test_node_config_initialization() { + let config = node_config("/tmp/raft"); + + assert_eq!( + config.cluster.db_root_dir, + std::path::PathBuf::from("/tmp/raft") + ); +} + +#[test] +fn test_node_config_different_paths() { + let path1 = "/data/raft1"; + let path2 = "/data/raft2"; + + let config1 = node_config(path1); + let config2 = node_config(path2); + + assert_eq!(config1.cluster.db_root_dir, std::path::PathBuf::from(path1)); + assert_eq!(config2.cluster.db_root_dir, std::path::PathBuf::from(path2)); + assert_ne!(config1.cluster.db_root_dir, config2.cluster.db_root_dir); +} + +#[test] +fn test_node_config_absolute_path() { + let path = "/absolute/path/to/db"; + let config = node_config(path); + + assert!(config.cluster.db_root_dir.is_absolute()); +} + +#[test] +fn test_node_config_relative_path() { + let path = "./relative/db"; + let config = node_config(path); + + assert_eq!(config.cluster.db_root_dir, std::path::PathBuf::from(path)); +} + +#[test] +fn test_generate_insert_commands_consistency() { + let ids = vec![10, 20, 30]; + let cmd1 = generate_insert_commands(ids.clone()); + let cmd2 = generate_insert_commands(ids); + + assert_eq!(cmd1.len(), cmd2.len()); +} + +#[test] +fn test_generate_delete_commands_consistency() { + let cmd1 = generate_delete_commands(5..=10); + let cmd2 = generate_delete_commands(5..=10); + + assert_eq!(cmd1.len(), cmd2.len()); +} + +#[test] +fn test_snapshot_config_immutable_after_creation() { + let dir = PathBuf::from("/tmp"); + let config = snapshot_config(dir.clone()); + + let max_entries = config.max_log_entries_before_snapshot; + let chunk_sz = config.chunk_size; + + assert_eq!(max_entries, 1); + assert_eq!(chunk_sz, 1024); +} + +#[test] +fn test_create_mixed_entries_ordering() { + let entries = create_mixed_entries(); + + assert!(entries[0].index < entries[1].index); + assert_eq!(entries[0].term, entries[1].term); +} diff --git a/d-engine-core/src/test_utils/entry_builder_test.rs b/d-engine-core/src/test_utils/entry_builder_test.rs new file mode 100644 index 00000000..17500c93 --- /dev/null +++ b/d-engine-core/src/test_utils/entry_builder_test.rs @@ -0,0 +1,214 @@ + +use crate::test_utils::EntryBuilder; +use d_engine_proto::common::{AddNode, RemoveNode, membership_change::Change}; + +#[test] +fn test_entry_builder_creation() { + let builder = EntryBuilder::new(1, 1); + let (_, entry) = builder.noop(); + + assert_eq!(entry.index, 1); + assert_eq!(entry.term, 1); +} + +#[test] +fn test_entry_builder_command_single() { + let builder = EntryBuilder::new(1, 1); + let (_, entry) = builder.command(b"test_command"); + + assert_eq!(entry.index, 1); + assert_eq!(entry.term, 1); + assert!(entry.payload.is_some()); +} + +#[test] +fn test_entry_builder_command_chain() { + let builder = EntryBuilder::new(1, 1); + let (builder, entry1) = builder.command(b"cmd1"); + let (_, entry2) = builder.command(b"cmd2"); + + assert_eq!(entry1.index, 1); + assert_eq!(entry2.index, 2); +} + +#[test] +fn test_entry_builder_noop_single() { + let builder = EntryBuilder::new(5, 3); + let (_, entry) = builder.noop(); + + assert_eq!(entry.index, 5); + assert_eq!(entry.term, 3); + assert!(entry.payload.is_some()); +} + +#[test] +fn test_entry_builder_config_add_node() { + let builder = EntryBuilder::new(1, 1); + let change = Change::AddNode(AddNode { + node_id: 2, + address: "127.0.0.1:8080".to_string(), + }); + let (_, entry) = builder.config(change); + + assert_eq!(entry.index, 1); + assert_eq!(entry.term, 1); + assert!(entry.payload.is_some()); +} + +#[test] +fn test_entry_builder_config_remove_node() { + let builder = EntryBuilder::new(10, 2); + let change = Change::RemoveNode(RemoveNode { node_id: 3 }); + let (_, entry) = builder.config(change); + + assert_eq!(entry.index, 10); + assert_eq!(entry.term, 2); + assert!(entry.payload.is_some()); +} + +#[test] +fn test_entry_builder_mixed_sequence() { + let builder = EntryBuilder::new(1, 1); + let (builder, cmd_entry) = builder.command(b"test"); + let (builder, noop_entry) = builder.noop(); + let change = Change::AddNode(AddNode { + node_id: 5, + address: "192.168.1.1:9000".to_string(), + }); + let (_, config_entry) = builder.config(change); + + assert_eq!(cmd_entry.index, 1); + assert_eq!(noop_entry.index, 2); + assert_eq!(config_entry.index, 3); + + assert_eq!(cmd_entry.term, 1); + assert_eq!(noop_entry.term, 1); + assert_eq!(config_entry.term, 1); +} + +#[test] +fn test_entry_builder_index_increments() { + let builder = EntryBuilder::new(100, 5); + let (builder, entry1) = builder.noop(); + let (builder, entry2) = builder.noop(); + let (builder, entry3) = builder.noop(); + let (_, entry4) = builder.noop(); + + assert_eq!(entry1.index, 100); + assert_eq!(entry2.index, 101); + assert_eq!(entry3.index, 102); + assert_eq!(entry4.index, 103); +} + +#[test] +fn test_entry_builder_term_constant() { + let builder = EntryBuilder::new(1, 5); + let (builder, entry1) = builder.noop(); + let (builder, entry2) = builder.command(b"data"); + let (_, entry3) = builder.noop(); + + assert_eq!(entry1.term, 5); + assert_eq!(entry2.term, 5); + assert_eq!(entry3.term, 5); +} + +#[test] +fn test_entry_builder_command_empty_data() { + let builder = EntryBuilder::new(1, 1); + let (_, entry) = builder.command(b""); + + assert!(entry.payload.is_some()); +} + +#[test] +fn test_entry_builder_command_large_data() { + let large_data = vec![0u8; 10000]; + let builder = EntryBuilder::new(1, 1); + let (_, entry) = builder.command(&large_data); + + assert!(entry.payload.is_some()); +} + +#[test] +fn test_entry_builder_high_index() { + let builder = EntryBuilder::new(u64::MAX - 2, 1); + let (builder, entry1) = builder.noop(); + let (_, entry2) = builder.noop(); + + assert_eq!(entry1.index, u64::MAX - 2); + assert_eq!(entry2.index, u64::MAX - 1); +} + +#[test] +fn test_entry_builder_high_term() { + let builder = EntryBuilder::new(1, u64::MAX); + let (_, entry) = builder.noop(); + + assert_eq!(entry.term, u64::MAX); +} + +#[test] +fn test_entry_builder_zero_index() { + let builder = EntryBuilder::new(0, 1); + let (_, entry) = builder.noop(); + + assert_eq!(entry.index, 0); +} + +#[test] +fn test_entry_builder_zero_term() { + let builder = EntryBuilder::new(1, 0); + let (_, entry) = builder.noop(); + + assert_eq!(entry.term, 0); +} + +#[test] +fn test_entry_builder_command_binary_data() { + let binary_data = vec![0xFF, 0xFE, 0xFD, 0xFC]; + let builder = EntryBuilder::new(1, 1); + let (_, entry) = builder.command(&binary_data); + + assert!(entry.payload.is_some()); +} + +#[test] +fn test_entry_builder_multiple_commands() { + let builder = EntryBuilder::new(1, 1); + let (builder, e1) = builder.command(b"cmd1"); + let (builder, e2) = builder.command(b"cmd2"); + let (builder, e3) = builder.command(b"cmd3"); + let (_, e4) = builder.command(b"cmd4"); + + assert_eq!( + vec![e1.index, e2.index, e3.index, e4.index], + vec![1, 2, 3, 4] + ); +} + +#[test] +fn test_entry_builder_payload_types() { + let builder = EntryBuilder::new(1, 1); + let (builder, cmd_entry) = builder.command(b"data"); + let (builder, noop_entry) = builder.noop(); + let change = Change::AddNode(AddNode { + node_id: 1, + address: "127.0.0.1:5000".to_string(), + }); + let (_, config_entry) = builder.config(change); + + assert!(cmd_entry.payload.is_some()); + assert!(noop_entry.payload.is_some()); + assert!(config_entry.payload.is_some()); +} + +#[test] +fn test_entry_builder_state_after_sequence() { + let builder = EntryBuilder::new(10, 5); + let (builder, _) = builder.noop(); + let (builder, _) = builder.command(b"test"); + let (_, last_entry) = builder.noop(); + + assert_eq!(last_entry.index, 12); + assert_eq!(last_entry.term, 5); +} diff --git a/d-engine-core/src/test_utils/mod.rs b/d-engine-core/src/test_utils/mod.rs index a40b45ea..eb838d66 100644 --- a/d-engine-core/src/test_utils/mod.rs +++ b/d-engine-core/src/test_utils/mod.rs @@ -5,6 +5,12 @@ mod entry_builder; mod mock; mod snapshot; +#[cfg(test)] +mod common_test; + +#[cfg(test)] +mod entry_builder_test; + pub use common::*; pub use entry_builder::*; pub use mock::*; From 30f5cafa486272fab61b8bfb906a977321828607 Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Sat, 1 Nov 2025 23:48:58 +0800 Subject: [PATCH 24/26] fix #167: fix fmt --- d-engine-core/src/test_utils/common_test.rs | 1 - d-engine-core/src/test_utils/entry_builder_test.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/d-engine-core/src/test_utils/common_test.rs b/d-engine-core/src/test_utils/common_test.rs index a0e8677a..e0a65c5e 100644 --- a/d-engine-core/src/test_utils/common_test.rs +++ b/d-engine-core/src/test_utils/common_test.rs @@ -1,4 +1,3 @@ - use crate::test_utils::{ create_config_entries, create_mixed_entries, generate_delete_commands, generate_insert_commands, node_config, snapshot_config, diff --git a/d-engine-core/src/test_utils/entry_builder_test.rs b/d-engine-core/src/test_utils/entry_builder_test.rs index 17500c93..23b33836 100644 --- a/d-engine-core/src/test_utils/entry_builder_test.rs +++ b/d-engine-core/src/test_utils/entry_builder_test.rs @@ -1,4 +1,3 @@ - use crate::test_utils::EntryBuilder; use d_engine_proto::common::{AddNode, RemoveNode, membership_change::Change}; From 323a373e183cdffb23df227350a2578663c8969b Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Sun, 2 Nov 2025 12:08:16 +0800 Subject: [PATCH 25/26] feat #167: make sure we only publish one crate: d-engine --- Cargo.lock | 8 + Cargo.toml | 3 +- Makefile | 75 +++++++ benches/d-engine-bench/Cargo.lock | 9 +- benches/d-engine-bench/Cargo.toml | 2 +- benches/d-engine-bench/src/main.rs | 8 +- d-engine-client/Cargo.toml | 1 + d-engine-core/Cargo.toml | 1 + d-engine-docs/Cargo.toml | 1 + d-engine-proto/Cargo.toml | 1 + d-engine-server/Cargo.toml | 2 + d-engine/Cargo.toml | 32 +++ d-engine/src/lib.rs | 192 ++++++++++++++++++ examples/client_usage/Cargo.lock | 9 +- examples/client_usage/Cargo.toml | 2 +- examples/client_usage/src/main.rs | 2 +- examples/rocksdb-cluster/Cargo.lock | 9 +- examples/rocksdb-cluster/Cargo.toml | 2 +- examples/rocksdb-cluster/src/main.rs | 4 +- examples/sled-cluster/Cargo.toml | 2 +- examples/sled-cluster/src/converters.rs | 6 +- examples/sled-cluster/src/main.rs | 2 +- examples/sled-cluster/src/sled_engine_test.rs | 2 +- .../sled-cluster/src/sled_state_machine.rs | 10 +- .../sled-cluster/src/sled_storage_engine.rs | 2 +- .../state_machine/sled_state_machine_test.rs | 8 +- .../sled_storage_engine_test.rs | 8 +- examples/three-nodes-cluster/Cargo.toml | 2 +- examples/three-nodes-cluster/src/main.rs | 6 +- scripts/pre-release-checklist.sh | 132 ------------ 30 files changed, 374 insertions(+), 169 deletions(-) create mode 100644 d-engine/Cargo.toml create mode 100644 d-engine/src/lib.rs delete mode 100755 scripts/pre-release-checklist.sh diff --git a/Cargo.lock b/Cargo.lock index f2bc3c9f..e4a3a97c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,6 +404,14 @@ dependencies = [ "typenum", ] +[[package]] +name = "d-engine" +version = "0.2.0" +dependencies = [ + "d-engine-client", + "d-engine-server", +] + [[package]] name = "d-engine-client" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 70f3f9f0..f9f6ff27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,12 @@ [workspace] members = [ + "d-engine", "d-engine-proto", "d-engine-core", "d-engine-client", "d-engine-docs", - "d-engine-server", + "d-engine-server" ] exclude = [ diff --git a/Makefile b/Makefile index 2072aa41..9d47aed0 100644 --- a/Makefile +++ b/Makefile @@ -467,6 +467,81 @@ fix: fmt-fix clippy-fix all: check @echo "$(GREEN)✓ Check suite completed$(NC)" +# ============================================================================ +# EXAMPLES AND BENCHMARKS VALIDATION +# ============================================================================ + +## check-examples Check all examples compile without errors +check-examples: + @echo "$(CYAN)Checking all examples...$(NC)" + @for example in examples/*/; do \ + if [ -f "$$example/Cargo.toml" ]; then \ + example_name=$$(basename "$$example"); \ + echo "$(YELLOW)→ Checking example: $$example_name$(NC)"; \ + (cd "$$example" && $(CARGO) check 2>&1) || { \ + echo "$(RED)✗ Example $$example_name failed$(NC)"; \ + exit 1; \ + }; \ + echo "$(GREEN)✓ Example $$example_name OK$(NC)"; \ + fi \ + done + @echo "$(GREEN)✓ All examples checked successfully$(NC)" + +## check-benches Check all benchmarks compile without errors +check-benches: + @echo "$(CYAN)Checking all benchmarks...$(NC)" + @for bench in benches/*/; do \ + if [ -f "$$bench/Cargo.toml" ]; then \ + bench_name=$$(basename "$$bench"); \ + echo "$(YELLOW)→ Checking benchmark: $$bench_name$(NC)"; \ + (cd "$$bench" && $(CARGO) check 2>&1) || { \ + echo "$(RED)✗ Benchmark $$bench_name failed$(NC)"; \ + exit 1; \ + }; \ + echo "$(GREEN)✓ Benchmark $$bench_name OK$(NC)"; \ + fi \ + done + @echo "$(GREEN)✓ All benchmarks checked successfully$(NC)" + +## check-all-projects Check workspace + examples + benchmarks +check-all-projects: check check-examples check-benches + @echo "" + @echo "$(GREEN)════════════════════════════════════════$(NC)" + @echo "$(GREEN)✓ ALL PROJECTS VALIDATED SUCCESSFULLY$(NC)" + @echo "$(GREEN)════════════════════════════════════════$(NC)" + +## build-examples Build all examples +build-examples: + @echo "$(CYAN)Building all examples...$(NC)" + @for example in examples/*/; do \ + if [ -f "$$example/Cargo.toml" ]; then \ + example_name=$$(basename "$$example"); \ + echo "$(YELLOW)→ Building example: $$example_name$(NC)"; \ + (cd "$$example" && $(CARGO) build 2>&1) || { \ + echo "$(RED)✗ Example $$example_name failed$(NC)"; \ + exit 1; \ + }; \ + echo "$(GREEN)✓ Example $$example_name built$(NC)"; \ + fi \ + done + @echo "$(GREEN)✓ All examples built successfully$(NC)" + +## test-examples Run tests in all examples +test-examples: + @echo "$(CYAN)Testing all examples...$(NC)" + @for example in examples/*/; do \ + if [ -f "$$example/Cargo.toml" ]; then \ + example_name=$$(basename "$$example"); \ + echo "$(YELLOW)→ Testing example: $$example_name$(NC)"; \ + (cd "$$example" && $(CARGO) test 2>&1) || { \ + echo "$(RED)✗ Example $$example_name tests failed$(NC)"; \ + exit 1; \ + }; \ + echo "$(GREEN)✓ Example $$example_name tests passed$(NC)"; \ + fi \ + done + @echo "$(GREEN)✓ All example tests passed$(NC)" + # ============================================================================ # DEFAULT TARGET # ============================================================================ diff --git a/benches/d-engine-bench/Cargo.lock b/benches/d-engine-bench/Cargo.lock index c1230a1e..65c5b84d 100644 --- a/benches/d-engine-bench/Cargo.lock +++ b/benches/d-engine-bench/Cargo.lock @@ -299,6 +299,13 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "d-engine" +version = "0.2.0" +dependencies = [ + "d-engine-client", +] + [[package]] name = "d-engine-bench" version = "0.2.0" @@ -306,7 +313,7 @@ dependencies = [ "atomic-shim", "bytes", "clap", - "d-engine-client", + "d-engine", "futures", "hdrhistogram", "rand", diff --git a/benches/d-engine-bench/Cargo.toml b/benches/d-engine-bench/Cargo.toml index 48ec7ef3..afddae6b 100644 --- a/benches/d-engine-bench/Cargo.toml +++ b/benches/d-engine-bench/Cargo.toml @@ -4,7 +4,7 @@ version = "0.2.0" edition = "2024" [dependencies] -d-engine-client = { path = "../../d-engine-client" } +d-engine = { path = "../../d-engine", features = ["client"] } tokio = { version = "1.44", features = ["full"] } diff --git a/benches/d-engine-bench/src/main.rs b/benches/d-engine-bench/src/main.rs index 13f6e84a..d413440f 100644 --- a/benches/d-engine-bench/src/main.rs +++ b/benches/d-engine-bench/src/main.rs @@ -7,10 +7,10 @@ use std::time::Instant; use clap::Parser; use clap::Subcommand; -use d_engine_client::Client; -use d_engine_client::ClientApiError; -use d_engine_client::ClientBuilder; -use d_engine_client::protocol::ReadConsistencyPolicy; +use d_engine::Client; +use d_engine::ClientApiError; +use d_engine::ClientBuilder; +use d_engine::protocol::ReadConsistencyPolicy; use hdrhistogram::Histogram; use rand::Rng; use rand::SeedableRng; diff --git a/d-engine-client/Cargo.toml b/d-engine-client/Cargo.toml index 95420a91..e9000448 100644 --- a/d-engine-client/Cargo.toml +++ b/d-engine-client/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/deventlab/d-engine" license = "MIT OR Apache-2.0" keywords = ["raft", "consensus", "client", "async"] categories = ["asynchronous", "network-programming"] +publish = false [package.metadata.docs.rs] all-features = true diff --git a/d-engine-core/Cargo.toml b/d-engine-core/Cargo.toml index 687baa82..53d5cb9e 100644 --- a/d-engine-core/Cargo.toml +++ b/d-engine-core/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/deventlab/d-engine" license = "MIT OR Apache-2.0" keywords = ["raft", "consensus", "algorithm"] categories = ["algorithms", "concurrency"] +publish = false [package.metadata.docs.rs] all-features = true diff --git a/d-engine-docs/Cargo.toml b/d-engine-docs/Cargo.toml index c7bce5ae..7369ae76 100644 --- a/d-engine-docs/Cargo.toml +++ b/d-engine-docs/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/deventlab/d-engine" license = "MIT OR Apache-2.0" keywords = ["raft", "consensus", "documentation"] categories = ["documentation"] +publish = false [package.metadata.docs.rs] all-features = true diff --git a/d-engine-proto/Cargo.toml b/d-engine-proto/Cargo.toml index d34d7c26..1fff816f 100644 --- a/d-engine-proto/Cargo.toml +++ b/d-engine-proto/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/deventlab/d-engine" license = "MIT OR Apache-2.0" keywords = ["raft", "protobuf", "grpc"] categories = ["encoding", "network-programming"] +publish = false [package.metadata.docs.rs] all-features = true diff --git a/d-engine-server/Cargo.toml b/d-engine-server/Cargo.toml index bc2fa338..9a69e5cd 100644 --- a/d-engine-server/Cargo.toml +++ b/d-engine-server/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/deventlab/d-engine" license = "MIT OR Apache-2.0" keywords = ["raft", "consensus", "distributed-systems", "async"] categories = ["asynchronous", "concurrency", "network-programming"] +publish = false [package.metadata.docs.rs] all-features = true @@ -16,6 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] test-utils = ["mockall", "tokio-tar", "async-compression", "nanoid"] +rocksdb = ["dep:rocksdb"] [dependencies] d-engine-proto = { workspace = true } diff --git a/d-engine/Cargo.toml b/d-engine/Cargo.toml new file mode 100644 index 00000000..f9e19499 --- /dev/null +++ b/d-engine/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "d-engine" +version = "0.2.0" +edition = "2024" +authors = ["Joshua Chi "] +description = "A lightweight and strongly consistent Raft consensus engine written in Rust. It is a base to build reliable and scalable distributed systems." +homepage = "https://github.com/deventlab/d-engine" +repository = "https://github.com/deventlab/d-engine" +license = "MIT OR Apache-2.0" +keywords = [ + "raft", + "consensus", + "distributed-systems", + "storage", + "fault-tolerance", +] +categories = ["concurrency", "asynchronous", "algorithms", "database"] +readme = "../README.md" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[features] +client = ["dep:d-engine-client"] +server = ["dep:d-engine-server"] +full = ["client", "server"] +rocksdb = ["server", "d-engine-server/rocksdb"] + +[dependencies] +d-engine-client = { workspace = true, optional = true } +d-engine-server = { workspace = true, optional = true } diff --git a/d-engine/src/lib.rs b/d-engine/src/lib.rs new file mode 100644 index 00000000..a1a088b0 --- /dev/null +++ b/d-engine/src/lib.rs @@ -0,0 +1,192 @@ +//! # d-engine +//! +//! A lightweight and strongly consistent Raft consensus engine written in Rust. +//! Build reliable and scalable distributed systems by embedding d-engine into your applications. +//! +//! ## Quick Start +//! +//! Add d-engine to your `Cargo.toml` with features you need: +//! +//! ```toml +//! # For client-only applications +//! d-engine = { version = "0.2", features = ["client"] } +//! +//! # For server/embedded applications +//! d-engine = { version = "0.2", features = ["server"] } +//! +//! # For applications that need both +//! d-engine = { version = "0.2", features = ["full"] } +//! ``` +//! +//! ## Features +//! +//! - **`client`** - Client library for connecting to d-engine servers +//! - **`server`** - Embed a d-engine Raft server into your application +//! - **`full`** - Both client and server capabilities +//! +//! ## Client Example +//! +//! ```rust,ignore +//! use d_engine::Client; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { +//! let client = Client::builder(vec![ +//! "http://localhost:9081".into(), +//! "http://localhost:9082".into(), +//! ]) +//! .build() +//! .await?; +//! +//! // Write data +//! client.kv().put("key", "value").await?; +//! +//! // Read data +//! let value = client.kv().get("key").await?; +//! println!("Value: {:?}", value); +//! +//! Ok(()) +//! } +//! ``` +//! +//! ## Server Example (Embedded) +//! +//! ```rust,ignore +//! use d_engine::{NodeBuilder, FileStorageEngine, FileStateMachine}; +//! use std::sync::Arc; +//! use std::path::PathBuf; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { +//! let (tx, rx) = tokio::sync::watch::channel(()); +//! +//! let storage = Arc::new(FileStorageEngine::new(PathBuf::from("/tmp/storage"))?); +//! let state_machine = Arc::new(FileStateMachine::new(PathBuf::from("/tmp/sm")).await?); +//! +//! let node = NodeBuilder::new(None, rx) +//! .storage_engine(storage) +//! .state_machine(state_machine) +//! .build() +//! .start_rpc_server() +//! .await +//! .ready()?; +//! +//! node.run().await?; +//! Ok(()) +//! } +//! ``` +//! +//! ## Architecture +//! +//! d-engine is designed with a modular architecture: +//! +//! - **d-engine-proto**: Protocol definitions (Protobuf/gRPC) +//! - **d-engine-core**: Core Raft algorithm implementation +//! - **d-engine-client**: Client library for cluster communication +//! - **d-engine-server**: Production-ready server runtime +//! +//! This main crate re-exports the public APIs from these components based on +//! enabled features, providing a unified interface for developers. + +#![warn(missing_docs)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +// ==================== Client API ==================== + +#[cfg(feature = "client")] +#[cfg_attr(docsrs, doc(cfg(feature = "client")))] +pub use d_engine_client::{ + // Main entry points + Client, + // Error types + ClientApiError, + ClientBuilder, + // Configuration + ClientConfig, + // Specialized clients + ClusterClient, + KvClient, +}; + +#[cfg(feature = "client")] +#[cfg_attr(docsrs, doc(cfg(feature = "client")))] +/// Protocol types for client operations +pub mod protocol { + pub use d_engine_client::protocol::{ClientResult, ReadConsistencyPolicy, WriteCommand}; +} + +#[cfg(feature = "client")] +#[cfg_attr(docsrs, doc(cfg(feature = "client")))] +/// Cluster management types +pub mod cluster_types { + pub use d_engine_client::cluster_types::{NodeMeta, NodeStatus}; +} + +// ==================== Server API ==================== + +#[cfg(feature = "server")] +#[cfg_attr(docsrs, doc(cfg(feature = "server")))] +pub use d_engine_server::{ + // Error types + Error, + // Storage implementations + FileStateMachine, + FileStorageEngine, + // Data types + HardState, + // Extension traits for custom implementations + LogStore, + MetaStore, + // Main entry points + Node, + NodeBuilder, + ProstError, + Result, + SnapshotError, + StateMachine, + StorageEngine, + StorageError, +}; + +#[cfg(all(feature = "server", feature = "rocksdb"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "server", feature = "rocksdb"))))] +pub use d_engine_server::{RocksDBStateMachine, RocksDBStorageEngine}; + +#[cfg(feature = "server")] +#[cfg_attr(docsrs, doc(cfg(feature = "server")))] +/// Common Raft protocol types +pub mod common { + pub use d_engine_server::common::{Entry, EntryPayload, LogId, entry_payload}; +} + +#[cfg(feature = "server")] +#[cfg_attr(docsrs, doc(cfg(feature = "server")))] +/// Client write command types +pub mod write_command { + pub use d_engine_server::client::{WriteCommand, write_command}; +} + +#[cfg(feature = "server")] +#[cfg_attr(docsrs, doc(cfg(feature = "server")))] +/// Server storage types +pub mod storage { + pub use d_engine_server::server_storage::SnapshotMetadata; +} + +// ==================== Convenience Re-exports ==================== + +/// Convenient prelude for quick imports +/// +/// Import everything you need with: +/// ```rust,ignore +/// use d_engine::prelude::*; +/// ``` +pub mod prelude { + #[cfg(feature = "client")] + pub use d_engine_client::{Client, ClientBuilder}; + + #[cfg(feature = "server")] + pub use d_engine_server::{ + FileStateMachine, FileStorageEngine, Node, NodeBuilder, StateMachine, StorageEngine, + }; +} diff --git a/examples/client_usage/Cargo.lock b/examples/client_usage/Cargo.lock index 4e1a8239..7b22f0fc 100644 --- a/examples/client_usage/Cargo.lock +++ b/examples/client_usage/Cargo.lock @@ -274,6 +274,13 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "d-engine" +version = "0.2.0" +dependencies = [ + "d-engine-client", +] + [[package]] name = "d-engine-client" version = "0.2.0" @@ -310,7 +317,7 @@ version = "0.2.0" dependencies = [ "anyhow", "clap", - "d-engine-client", + "d-engine", "env_logger", "tokio", ] diff --git a/examples/client_usage/Cargo.toml b/examples/client_usage/Cargo.toml index 17d34b4a..9e09b914 100644 --- a/examples/client_usage/Cargo.toml +++ b/examples/client_usage/Cargo.toml @@ -4,7 +4,7 @@ version = "0.2.0" edition = "2021" [dependencies] -d-engine-client = { path = "../../d-engine-client" } +d-engine = { path = "../../d-engine", features = ["client"] } clap = { version = "4.0", features = ["derive"] } tokio = { version = "1.36.0", features = ["full"] } diff --git a/examples/client_usage/src/main.rs b/examples/client_usage/src/main.rs index a8d8c87d..d452e5af 100644 --- a/examples/client_usage/src/main.rs +++ b/examples/client_usage/src/main.rs @@ -1,7 +1,7 @@ use anyhow::Result; use clap::Parser; use clap::Subcommand; -use d_engine_client::{ +use d_engine::{ cluster_types::{NodeMeta, NodeStatus}, protocol::{ClientResult, ReadConsistencyPolicy}, Client, ClientApiError, ClientBuilder, diff --git a/examples/rocksdb-cluster/Cargo.lock b/examples/rocksdb-cluster/Cargo.lock index 07d8653a..a9f4d428 100644 --- a/examples/rocksdb-cluster/Cargo.lock +++ b/examples/rocksdb-cluster/Cargo.lock @@ -462,6 +462,13 @@ dependencies = [ "typenum", ] +[[package]] +name = "d-engine" +version = "0.2.0" +dependencies = [ + "d-engine-server", +] + [[package]] name = "d-engine-core" version = "0.1.0" @@ -1684,7 +1691,7 @@ dependencies = [ "async-trait", "bincode 2.0.1", "bytes", - "d-engine-server", + "d-engine", "metrics-exporter-prometheus", "prost", "tokio", diff --git a/examples/rocksdb-cluster/Cargo.toml b/examples/rocksdb-cluster/Cargo.toml index cf6d04f7..212a2283 100644 --- a/examples/rocksdb-cluster/Cargo.toml +++ b/examples/rocksdb-cluster/Cargo.toml @@ -4,8 +4,8 @@ version = "0.2.0" edition = "2024" [dependencies] +d-engine = { path = "../../d-engine", features = ["server", "rocksdb"] } # d-engine = "0.1.4" -d-engine-server = { path = "../../d-engine-server", features = ["rocksdb"] } bytes = "1.10" prost = { version = "0.13", default-features = false } diff --git a/examples/rocksdb-cluster/src/main.rs b/examples/rocksdb-cluster/src/main.rs index cbc13ef2..311bc60f 100644 --- a/examples/rocksdb-cluster/src/main.rs +++ b/examples/rocksdb-cluster/src/main.rs @@ -1,5 +1,5 @@ -use d_engine_server::NodeBuilder; -use d_engine_server::{RocksDBStateMachine, RocksDBStorageEngine}; +use d_engine::NodeBuilder; +use d_engine::{RocksDBStateMachine, RocksDBStorageEngine}; use std::env; use std::error::Error; use std::fs::OpenOptions; diff --git a/examples/sled-cluster/Cargo.toml b/examples/sled-cluster/Cargo.toml index 8d78a00c..0f3f4551 100644 --- a/examples/sled-cluster/Cargo.toml +++ b/examples/sled-cluster/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.3" edition = "2021" [dependencies] +d-engine = { path = "../../d-engine", features = ["server"] } # d-engine = "0.1.4" -d-engine-server = { path = "../../d-engine-server" } sled = { version = "0.34.7", features = [ "compression", diff --git a/examples/sled-cluster/src/converters.rs b/examples/sled-cluster/src/converters.rs index ca8864fd..ad4a1da9 100644 --- a/examples/sled-cluster/src/converters.rs +++ b/examples/sled-cluster/src/converters.rs @@ -3,7 +3,7 @@ //! This module provides utility functions for serializing/deserializing //! u64 values to/from byte arrays used as keys in sled. -use d_engine_server::StorageError; +use d_engine::StorageError; use sled::IVec; /// Convert a u64 value to an 8-byte big-endian vector. @@ -26,7 +26,7 @@ pub(crate) fn safe_kv(value: u64) -> Vec { /// # Returns /// `Ok(u64)` if the slice is exactly 8 bytes, `Err` otherwise #[inline] -pub(crate) fn safe_vk(bytes: &[u8]) -> Result { +pub(crate) fn safe_vk(bytes: &[u8]) -> Result { if bytes.len() != 8 { return Err(StorageError::DbError(format!( "Invalid byte length: expected 8, got {}", @@ -42,6 +42,6 @@ pub(crate) fn safe_vk(bytes: &[u8]) -> Result Result { +pub(crate) fn safe_vk_ivec(ivec: IVec) -> Result { safe_vk(&ivec[..]) } diff --git a/examples/sled-cluster/src/main.rs b/examples/sled-cluster/src/main.rs index a8c8eb89..14106b96 100644 --- a/examples/sled-cluster/src/main.rs +++ b/examples/sled-cluster/src/main.rs @@ -1,4 +1,4 @@ -use d_engine_server::NodeBuilder; +use d_engine::NodeBuilder; use sled_demo::{SledStateMachine, SledStorageEngine}; use std::env; use std::error::Error; diff --git a/examples/sled-cluster/src/sled_engine_test.rs b/examples/sled-cluster/src/sled_engine_test.rs index 128d37cf..12d0e277 100644 --- a/examples/sled-cluster/src/sled_engine_test.rs +++ b/examples/sled-cluster/src/sled_engine_test.rs @@ -1,6 +1,6 @@ use super::*; use bytes::{Bytes, BytesMut}; -use d_engine_server::{ +use d_engine::{ client::{ write_command::{Insert, Operation}, WriteCommand, diff --git a/examples/sled-cluster/src/sled_state_machine.rs b/examples/sled-cluster/src/sled_state_machine.rs index 0302659b..b3f8b95f 100644 --- a/examples/sled-cluster/src/sled_state_machine.rs +++ b/examples/sled-cluster/src/sled_state_machine.rs @@ -4,11 +4,13 @@ use arc_swap::ArcSwap; use async_trait::async_trait; use bincode::config; use bytes::Bytes; -use d_engine_server::{ - client::write_command::{Delete, Insert, Operation}, - client::WriteCommand, +use d_engine::{ common::{entry_payload::Payload, Entry, LogId}, - server_storage::SnapshotMetadata, + storage::SnapshotMetadata, + write_command::{ + write_command::{Delete, Insert, Operation}, + WriteCommand, + }, Result, SnapshotError, StateMachine, StorageError, }; use parking_lot::Mutex; diff --git a/examples/sled-cluster/src/sled_storage_engine.rs b/examples/sled-cluster/src/sled_storage_engine.rs index a86341af..85c7c986 100644 --- a/examples/sled-cluster/src/sled_storage_engine.rs +++ b/examples/sled-cluster/src/sled_storage_engine.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use bincode::config; use bytes::Bytes; -use d_engine_server::{ +use d_engine::{ common::{Entry, LogId}, Error, HardState, LogStore, MetaStore, ProstError, Result, StorageEngine, StorageError, }; diff --git a/examples/sled-cluster/src/state_machine/sled_state_machine_test.rs b/examples/sled-cluster/src/state_machine/sled_state_machine_test.rs index 6e7e1818..9a2484c1 100644 --- a/examples/sled-cluster/src/state_machine/sled_state_machine_test.rs +++ b/examples/sled-cluster/src/state_machine/sled_state_machine_test.rs @@ -20,10 +20,10 @@ use d_engine::test_utils::generate_insert_commands; use d_engine::test_utils::reset_dbs; use d_engine::test_utils::reuse_dbs; use d_engine::StateMachine; -use d_engine_proto::common::Entry; -use d_engine_proto::common::EntryPayload; -use d_engine_proto::common::LogId; -use d_engine_proto::storage::SnapshotMetadata; +use d_engine::common::Entry; +use d_engine::common::EntryPayload; +use d_engine::common::LogId; +use d_engine::storage::SnapshotMetadata; pub fn setup_raft_components( db_path: &str, diff --git a/examples/sled-cluster/src/storage_engine/sled_storage_engine_test.rs b/examples/sled-cluster/src/storage_engine/sled_storage_engine_test.rs index 7f4a48e0..d33ea1a8 100644 --- a/examples/sled-cluster/src/storage_engine/sled_storage_engine_test.rs +++ b/examples/sled-cluster/src/storage_engine/sled_storage_engine_test.rs @@ -6,10 +6,10 @@ use d_engine::MetaStore; use d_engine::ProstError; use d_engine::StorageEngine; use d_engine::SystemError; -use d_engine_proto::common::Entry; -use d_engine_proto::common::EntryPayload; -use d_engine_proto::common::LogId; -use d_engine_proto::election::VotedFor; +use d_engine::common::Entry; +use d_engine::common::EntryPayload; +use d_engine::common::LogId; +use d_engine::election::VotedFor; use std::ops::RangeInclusive; use std::sync::Arc; use tempfile::TempDir; diff --git a/examples/three-nodes-cluster/Cargo.toml b/examples/three-nodes-cluster/Cargo.toml index 94703334..b7953eba 100644 --- a/examples/three-nodes-cluster/Cargo.toml +++ b/examples/three-nodes-cluster/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.4" edition = "2021" [dependencies] +d-engine = { path = "../../d-engine", features = ["server", "rocksdb"] } # d-engine = "0.1.1" -d-engine-server = { path = "../../d-engine-server", features = ["rocksdb"] } # d-engine = { git = "https://github.com/deventlab/d-engine.git", branch = "feature/79-snapshot" } metrics-exporter-prometheus = "0.17.2" diff --git a/examples/three-nodes-cluster/src/main.rs b/examples/three-nodes-cluster/src/main.rs index 2f6a4434..31d82088 100644 --- a/examples/three-nodes-cluster/src/main.rs +++ b/examples/three-nodes-cluster/src/main.rs @@ -1,8 +1,8 @@ -use d_engine_server::node::NodeBuilder; +use d_engine::NodeBuilder; // use d_engine::FileStateMachine; // use d_engine::FileStorageEngine; -use d_engine_server::RocksDBStateMachine; -use d_engine_server::RocksDBStorageEngine; +use d_engine::RocksDBStateMachine; +use d_engine::RocksDBStorageEngine; use std::env; use std::error::Error; use std::fs::OpenOptions; diff --git a/scripts/pre-release-checklist.sh b/scripts/pre-release-checklist.sh deleted file mode 100755 index bff51705..00000000 --- a/scripts/pre-release-checklist.sh +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env bash -# pre-release-checklist.sh -set -euxo pipefail - -# Add color output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -function header() { - echo -e "${YELLOW}\n==> $1${NC}" -} - -function success() { - echo -e "${GREEN}[✓] $1${NC}" -} - -function error() { - echo -e "${RED}[✗] $1${NC}" - exit 1 -} - -# -------------------------- -# 1. Test Suite Verification -# -------------------------- -header "Running test suite" - -TEST_TYPES=( - "Unit Tests: cargo test --lib -- --nocapture" - "Integration Tests: cargo test --tests -- --nocapture" - "Documentation Tests: cargo test --doc" - "Benchmark Tests: cargo bench -- --nocapture" -) - -for test_case in "${TEST_TYPES[@]}"; do - IFS=':' read -r test_name test_cmd <<< "$test_case" - echo -e "\n${YELLOW}▶ Running $test_name${NC}" - - if eval "$test_cmd"; then - success "$test_name passed" - else - error "$test_name failed" - fi -done - -# -------------------------- -# Final Report -# -------------------------- -header "Test Summary" -echo -e "${GREEN}All checks passed! Ready for release.${NC}" - - -# -------------------------- -# 2. Code quality checks -# -------------------------- -header "Running code quality checks" -cargo clippy --all-targets --all-features -- -D warnings || error "Clippy found issues" -cargo fmt --all -- --check || error "Code formatting issues found" - - -# -------------------------- -# 3. CHANGELOG reminder -# -------------------------- -header "Checking CHANGELOG updates" -if git diff --exit-code --name-only CHANGELOG.md; then - success "No changes detected in CHANGELOG.md." -else - success "CHANGELOG.md is updated" -fi - -# # -------------------------- -# # 4. 3-node cluster example test -# # -------------------------- -# header "Testing 3-node cluster example" -# ( -# cd examples/three-nodes-cluster || error "Cluster example directory not found" - -# # Clean previous runs -# make clean - -# # Start the cluster and capture logs -# LOG_FILE="logs/leader_detection.log" -# mkdir -p logs -# make start-cluster > "$LOG_FILE" 2>&1 & -# CLUSTER_PID=$! - -# # Set the detection timeout (in seconds) -# TIMEOUT=30 -# LEADER_DETECTED=0 - -# # Real-time monitoring log -# timeout $TIMEOUTs tail -Fn0 "$LOG_FILE" | \ -# while read -r line -# do -# if echo "$line" | grep -q "switch to Leader now."; then -# LEADER_DETECTED=1 - -# # Get and terminate all demo processes -# pids=$(pgrep -f "target/release/demo") -# if [ -n "$pids" ]; then -# echo "Killing demo processes: $pids" -# kill $pids -# fi - -# # Terminate the log monitoring loop -# pkill -P $$ timeout -# break -# fi -# done - -# # Handling timeouts -# if [ $LEADER_DETECTED -eq 0 ]; then -# error "Leader not detected within $TIMEOUT seconds" -# fi - -# # Wait for the cluster process to exit -# wait $CLUSTER_PID - -# # Verify that the process has terminated -# if pgrep -f "target/release/demo" >/dev/null; then -# error "Failed to kill all demo processes" -# fi - -# success "Cluster test passed" -# ) || error "Cluster test failed" - - -# -------------------------- -# All checks passed -# -------------------------- -echo -e "${GREEN}\nAll pre-release checks passed! Ready for deployment.${NC}" \ No newline at end of file From 79c9df12af35e8998aeae9dea1c8533855bfc306 Mon Sep 17 00:00:00 2001 From: Joshua Chi Date: Wed, 5 Nov 2025 17:11:22 +0800 Subject: [PATCH 26/26] fix #175: apply code review suggestions --- .github/workflows/ci.yml | 2 +- d-engine-client/src/kv_test.rs | 10 - d-engine-client/src/lib.rs | 2 + d-engine-client/src/mock_rpc.rs | 6 +- d-engine-client/src/proto/client_ext.rs | 102 ++- d-engine-client/src/utils.rs | 28 +- d-engine-client/src/utils_test.rs | 43 ++ .../src/election/election_handler_test.rs | 682 ------------------ d-engine-core/src/election/mod.rs | 3 - d-engine-core/src/errors_test.rs | 482 +++++++++++++ d-engine-core/src/lib.rs | 2 + .../backgroup_snapshot_transfer_test.rs | 4 +- d-engine-core/src/raft.rs | 60 +- .../default_state_machine_handler_test.rs | 16 +- d-engine-core/src/test_utils/mock/mock_rpc.rs | 8 +- d-engine-core/src/test_utils/snapshot.rs | 4 +- .../src/network/grpc/grpc_transport_test.rs | 4 +- d-engine-server/src/node/builder.rs | 8 +- d-engine-server/src/node/mod.rs | 2 +- .../src/test_utils/mock/mock_node_builder.rs | 12 +- .../src/test_utils/mock/mock_rpc_services.rs | 533 -------------- .../raft_role/candidate_state_test.rs | 4 +- .../components/raft_role/leader_state_test.rs | 4 +- d-engine-server/tests/components/raft_test.rs | 10 +- 24 files changed, 742 insertions(+), 1289 deletions(-) create mode 100644 d-engine-client/src/utils_test.rs delete mode 100644 d-engine-core/src/election/election_handler_test.rs create mode 100644 d-engine-core/src/errors_test.rs delete mode 100644 d-engine-server/src/test_utils/mock/mock_rpc_services.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3eb80397..7d542ed8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,7 @@ jobs: - name: Run tests with nextest and generate coverage run: | - cargo llvm-cov nextest --all-features --workspace --lcov --output-path lcov.info --ignore-filename-regex "src/generated/.*|src/errors.rs|d-engine-core/src/event.rs" + cargo llvm-cov nextest --all-features --workspace --lcov --output-path lcov.info --ignore-filename-regex "src/generated/.*|src/errors.rs" - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 diff --git a/d-engine-client/src/kv_test.rs b/d-engine-client/src/kv_test.rs index a661370c..645f6541 100644 --- a/d-engine-client/src/kv_test.rs +++ b/d-engine-client/src/kv_test.rs @@ -1,6 +1,5 @@ use arc_swap::ArcSwap; use bytes::Bytes; -use d_engine_proto::common::NodeRole; use std::sync::Arc; use std::vec; use tokio::sync::oneshot; @@ -16,10 +15,8 @@ use crate::mock_rpc_service::MockNode; use d_engine_proto::client::ClientResponse; use d_engine_proto::client::ClientResult; use d_engine_proto::client::ReadConsistencyPolicy; -use d_engine_proto::common::NodeStatus; use d_engine_proto::error::ErrorCode; use d_engine_proto::server::cluster::ClusterMembership; -use d_engine_proto::server::cluster::NodeMeta; #[tokio::test] #[traced_test] @@ -130,13 +127,6 @@ async fn test_get_success() { }))); // Set up mock response - let mut leader_node = NodeMeta { - id: 1, - role: NodeRole::Leader.into(), - address: "127.0.0.1:50051".to_string(), - status: NodeStatus::Active.into(), - }; - leader_node.role = NodeRole::Leader.into(); let result = client.get_with_policy(key, Some(ReadConsistencyPolicy::LinearizableRead)).await; println!("{:?}", &result); diff --git a/d-engine-client/src/lib.rs b/d-engine-client/src/lib.rs index e36b95ca..fa9b87e9 100644 --- a/d-engine-client/src/lib.rs +++ b/d-engine-client/src/lib.rs @@ -95,6 +95,8 @@ mod mock_rpc; mod mock_rpc_service; #[cfg(test)] mod pool_test; +#[cfg(test)] +mod utils_test; /// Main entry point for interacting with the d_engine cluster /// diff --git a/d-engine-client/src/mock_rpc.rs b/d-engine-client/src/mock_rpc.rs index 14c73508..001022d2 100644 --- a/d-engine-client/src/mock_rpc.rs +++ b/d-engine-client/src/mock_rpc.rs @@ -79,9 +79,7 @@ impl ClusterManagementService for MockRpcService { match &self.expected_join_cluster_response { Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), Some(Err(status)) => Err(status.clone()), - None => Err(tonic::Status::unknown( - "No mock get_cluster_metadata response set", - )), + None => Err(tonic::Status::unknown("No mock join_cluster response set")), } } @@ -93,7 +91,7 @@ impl ClusterManagementService for MockRpcService { Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), Some(Err(status)) => Err(status.clone()), None => Err(tonic::Status::unknown( - "No mock get_cluster_metadata response set", + "No mock discover_leader response set", )), } } diff --git a/d-engine-client/src/proto/client_ext.rs b/d-engine-client/src/proto/client_ext.rs index 701bf2d4..0af1f2f1 100644 --- a/d-engine-client/src/proto/client_ext.rs +++ b/d-engine-client/src/proto/client_ext.rs @@ -61,8 +61,20 @@ impl ClientResponseExt for ClientResponse { }) .collect(), _ => { - error!("Invalid response type for read operation"); - unreachable!() + let found = match &self.success_result { + Some(SuccessResult::WriteAck(_)) => "WriteAck", + None => "None", + _ => "Unknown", + }; + error!( + "Unexpected response type for read operation: expected ReadData, found {}", + found + ); + Err(ClientApiError::Protocol { + code: d_engine_proto::error::ErrorCode::InvalidResponse, + message: format!("Unexpected response type: expected ReadData, found {found}",), + supported_versions: None, + }) } } } @@ -78,3 +90,89 @@ impl ClientResponseExt for ClientResponse { } } } + +#[cfg(test)] +mod tests { + use super::*; + use bytes::Bytes; + use d_engine_proto::client::{ReadResults, client_response::SuccessResult}; + + #[test] + fn test_into_read_results_success() { + let response = ClientResponse { + error: ErrorCode::Success as i32, + metadata: None, + success_result: Some(SuccessResult::ReadData(ReadResults { + results: vec![ClientResult { + key: Bytes::from(vec![1, 2, 3]), + value: Bytes::from(vec![4, 5, 6]), + }], + })), + }; + + let result = response.into_read_results(); + assert!(result.is_ok()); + let data = result.unwrap(); + assert_eq!(data.len(), 1); + assert!(data[0].is_some()); + } + + #[test] + fn test_into_read_results_wrong_variant_write_ack() { + let response = ClientResponse { + error: ErrorCode::Success as i32, + metadata: None, + success_result: Some(SuccessResult::WriteAck(true)), + }; + + let result = response.into_read_results(); + assert!(result.is_err()); + + if let Err(ClientApiError::Protocol { code, message, .. }) = result { + assert_eq!(code, ErrorCode::InvalidResponse); + assert!(message.contains("expected ReadData")); + assert!(message.contains("found WriteAck")); + } else { + panic!("Expected Protocol error"); + } + } + + #[test] + fn test_into_read_results_none_variant() { + let response = ClientResponse { + error: ErrorCode::Success as i32, + metadata: None, + success_result: None, + }; + + let result = response.into_read_results(); + assert!(result.is_err()); + + if let Err(ClientApiError::Protocol { code, message, .. }) = result { + assert_eq!(code, ErrorCode::InvalidResponse); + assert!(message.contains("expected ReadData")); + assert!(message.contains("found None")); + } else { + panic!("Expected Protocol error"); + } + } + + #[test] + fn test_into_read_results_with_error_code() { + let response = ClientResponse { + error: ErrorCode::NotLeader as i32, + metadata: None, + success_result: Some(SuccessResult::ReadData(ReadResults { results: vec![] })), + }; + + let result = response.into_read_results(); + assert!(result.is_err()); + + // Should fail at validate_error() before checking success_result + if let Err(ClientApiError::Business { code, .. }) = result { + assert_eq!(code, ErrorCode::NotLeader); + } else { + panic!("Expected Business error with NotLeader code"); + } + } +} diff --git a/d-engine-client/src/utils.rs b/d-engine-client/src/utils.rs index 52e4d7ad..bf7ec87d 100644 --- a/d-engine-client/src/utils.rs +++ b/d-engine-client/src/utils.rs @@ -13,10 +13,28 @@ pub fn get_duration_since_epoch() -> Duration { SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards") } -/// accept ip either like 127.0.0.1 or docker host name: node1 +/// Normalizes an address string by ensuring it has a proper scheme prefix. +/// +/// This function: +/// - Detects and preserves existing "http://" or "https://" schemes +/// - Defaults to "http://" when no scheme is present +/// - Prevents scheme duplication +/// +/// # Examples +/// - "127.0.0.1:9000" -> "http://127.0.0.1:9000" +/// - "http://127.0.0.1:9000" -> "http://127.0.0.1:9000" +/// - "https://127.0.0.1:9000" -> "https://127.0.0.1:9000" +/// - "node1:9000" -> "http://node1:9000" pub(crate) fn address_str(addr: &str) -> String { - // Strip existing "http://" or "https://" prefixes if duplicated. - let normalized = addr.trim_start_matches("http://").trim_start_matches("https://"); - // Re-add a single "http://" prefix (or use HTTPS if needed). - format!("http://{normalized}") + // Detect if the address already has a scheme + if addr.starts_with("https://") { + // Preserve HTTPS scheme + addr.to_string() + } else if addr.starts_with("http://") { + // Preserve HTTP scheme + addr.to_string() + } else { + // No scheme present, default to HTTP + format!("http://{addr}",) + } } diff --git a/d-engine-client/src/utils_test.rs b/d-engine-client/src/utils_test.rs new file mode 100644 index 00000000..546fef07 --- /dev/null +++ b/d-engine-client/src/utils_test.rs @@ -0,0 +1,43 @@ +use super::*; + +#[test] +fn test_address_str_no_scheme() { + assert_eq!(address_str("127.0.0.1:9000"), "http://127.0.0.1:9000"); + assert_eq!(address_str("node1:9000"), "http://node1:9000"); + assert_eq!(address_str("localhost:8080"), "http://localhost:8080"); +} + +#[test] +fn test_address_str_preserves_http() { + assert_eq!( + address_str("http://127.0.0.1:9000"), + "http://127.0.0.1:9000" + ); + assert_eq!(address_str("http://node1:9000"), "http://node1:9000"); +} + +#[test] +fn test_address_str_preserves_https() { + assert_eq!( + address_str("https://127.0.0.1:9000"), + "https://127.0.0.1:9000" + ); + assert_eq!(address_str("https://node1:9000"), "https://node1:9000"); + assert_eq!( + address_str("https://secure.example.com:443"), + "https://secure.example.com:443" + ); +} + +#[test] +fn test_address_str_no_duplication() { + // Ensure we don't accidentally create double schemes + assert_eq!( + address_str("http://127.0.0.1:9000"), + "http://127.0.0.1:9000" + ); + assert_eq!( + address_str("https://127.0.0.1:9000"), + "https://127.0.0.1:9000" + ); +} diff --git a/d-engine-core/src/election/election_handler_test.rs b/d-engine-core/src/election/election_handler_test.rs deleted file mode 100644 index 79876feb..00000000 --- a/d-engine-core/src/election/election_handler_test.rs +++ /dev/null @@ -1,682 +0,0 @@ -use std::sync::Arc; - -use tokio::sync::watch; -use tracing::debug; -use tracing_test::traced_test; - -use crate::ConsensusError; -use crate::ElectionCore; -use crate::ElectionError; -use crate::ElectionHandler; -use crate::Error; -use crate::MockMembership; -use crate::MockRaftLog; -use crate::MockTransport; -use crate::VoteResult; -use crate::alias::ROF; -use crate::alias::TROF; -use crate::test_utils::MockTypeConfig; -use crate::test_utils::mock_membership; -use crate::test_utils::setup_raft_components; -use d_engine_proto::common::LogId; -use d_engine_proto::common::NodeRole::Follower; -use d_engine_proto::common::NodeStatus; -use d_engine_proto::server::cluster::NodeMeta; -use d_engine_proto::server::election::VoteRequest; -use d_engine_proto::server::election::VoteResponse; -use d_engine_proto::server::election::VotedFor; - -struct TestConext { - election_handler: ElectionHandler, - raft_log_mock: ROF, -} -async fn setup() -> TestConext { - let election_handler = ElectionHandler::::new(1); - - let raft_log_mock: ROF = MockRaftLog::new(); - - TestConext { - election_handler, - raft_log_mock, - } -} - -/// # Case 1: Receive election failed error if there is zero peers -/// -/// ## Validation Criterias: -/// 1. Receive ElectionError::QuorumFailure error -#[tokio::test] -#[traced_test] -async fn test_broadcast_vote_requests_case1() { - // 1. Create a ElectionHandler instance - let (_graceful_tx, _graceful_rx) = watch::channel(()); - let ctx = setup_raft_components("/tmp/test_broadcast_vote_requests_case1", None, false); - - let election_handler = ElectionHandler::::new(1); - - // 2. - let term = 1; - let mut raft_log_mock: ROF = MockRaftLog::new(); - raft_log_mock - .expect_last_log_id() - .times(0) - .returning(|| Some(LogId { index: 1, term: 1 })); - - let mut transport_mock: TROF = MockTransport::new(); - transport_mock.expect_send_vote_requests().times(0).returning(|_, _, _| { - Ok(VoteResult { - peer_ids: vec![2].into_iter().collect(), - responses: vec![Ok(VoteResponse { - term: 1, - vote_granted: false, - last_log_index: 1, - last_log_term: 1, - })], - }) - }); - - let err = election_handler - .broadcast_vote_requests( - term, - Arc::new(mock_membership()), - &Arc::new(raft_log_mock), - &Arc::new(transport_mock), - &ctx.arc_node_config, - ) - .await - .unwrap_err(); - - debug!(?err); - - assert!(matches!( - err, - Error::Consensus(ConsensusError::Election( - ElectionError::NoVotingMemberFound { candidate_id: _ } - )) - )); -} - -/// # Case 2: Test failed to receive majority peers' failed vote -/// -/// ## Validation criterias: -/// 1. Test should receive Err(ElectionError::LogConflict) -#[tokio::test] -#[traced_test] -async fn test_broadcast_vote_requests_case2() { - let (_graceful_tx, _graceful_rx) = watch::channel(()); - let ctx = setup_raft_components("/tmp/test_broadcast_vote_requests_case2", None, false); - let mut test_context = setup().await; - test_context - .raft_log_mock - .expect_last_log_id() - .times(0) - .returning(|| Some(LogId { index: 1, term: 1 })); - - let mut transport_mock: TROF = MockTransport::new(); - transport_mock.expect_send_vote_requests().times(0).returning(|_, _, _| { - Ok(VoteResult { - peer_ids: vec![2].into_iter().collect(), - responses: vec![Ok(VoteResponse { - term: 1, - vote_granted: false, - last_log_index: 1, - last_log_term: 1, - })], - }) - }); - let term = 1; - - let e = test_context - .election_handler - .broadcast_vote_requests( - term, - Arc::new(mock_membership()), - &Arc::new(test_context.raft_log_mock), - &Arc::new(transport_mock), - &ctx.arc_node_config, - ) - .await - .unwrap_err(); - - if let Error::Consensus(ConsensusError::Election(ElectionError::LogConflict { - index, - expected_term, - actual_term, - })) = e - { - assert_eq!(index, 1); - assert_eq!(actual_term, 1); - assert_eq!(expected_term, 1); - } -} -/// # Case 3: Test after receiving majority peers' success vote -/// -/// ## Validation criterias: -/// 1. Test should receive Ok(()) -#[tokio::test] -#[traced_test] -async fn test_broadcast_vote_requests_case3() { - let (_graceful_tx, _graceful_rx) = watch::channel(()); - let ctx = setup_raft_components("/tmp/test_broadcast_vote_requests_case3", None, false); - let mut test_context = setup().await; - test_context - .raft_log_mock - .expect_last_log_id() - .times(1) - .returning(|| Some(LogId { index: 1, term: 1 })); - let mut transport_mock: TROF = MockTransport::new(); - transport_mock.expect_send_vote_requests().times(1).returning(|_, _, _| { - Ok(VoteResult { - peer_ids: vec![2].into_iter().collect(), - responses: vec![Ok(VoteResponse { - term: 1, - vote_granted: true, - last_log_index: 1, - last_log_term: 1, - })], - }) - }); - - let term = 1; - let mut membership = MockMembership::new(); - membership.expect_voters().returning(move || { - vec![NodeMeta { - id: 2, - address: "http://127.0.0.1:55001".to_string(), - role: Follower.into(), - status: NodeStatus::Active.into(), - }] - }); - let r = test_context - .election_handler - .broadcast_vote_requests( - term, - Arc::new(membership), - &Arc::new(test_context.raft_log_mock), - &Arc::new(transport_mock), - &ctx.arc_node_config, - ) - .await; - debug!("test_broadcast_vote_requests_case3: {:?}", &r); - assert!(r.is_ok()) -} - -/// # Case 4: Test if vote response returns higher last_log_term -// ## Setup: -// 1. prepare one peers, which returns failed response -// 2. Peer1 returns higher term of last log index, -// -// ## Criterias: -// 1. return Err(ElectionError::HigherTerm) -#[tokio::test] -#[traced_test] -async fn test_broadcast_vote_requests_case4() { - let (_graceful_tx, _graceful_rx) = watch::channel(()); - let ctx = setup_raft_components("/tmp/test_broadcast_vote_requests_case4", None, false); - let test_context = setup().await; - - let my_last_log_term = 3; - let transport_mock: TROF = MockTransport::new(); - - let e = test_context - .election_handler - .broadcast_vote_requests( - my_last_log_term, - Arc::new(mock_membership()), - &Arc::new(test_context.raft_log_mock), - &Arc::new(transport_mock), - &ctx.arc_node_config, - ) - .await - .unwrap_err(); - - debug!("test_broadcast_vote_requests_case4: {:?}", &e); - - if let Error::Consensus(ConsensusError::Election(ElectionError::HigherTerm(higher_term))) = e { - assert_eq!(higher_term, my_last_log_term + 1); - } -} - -/// # Case 5: Test if vote response returns higher last_log_index -// ## Setup: -// 1. prepare one peers, which returns failed response -// 2. Peer1 returns last log term is the same as candidate one, but last log index is higher than -// candidate last log index, -// -// ## Criterias: -// 1. return Err(ElectionError::LogConflict) -#[tokio::test] -#[traced_test] -async fn test_broadcast_vote_requests_case5() { - let (_graceful_tx, _graceful_rx) = watch::channel(()); - let ctx = setup_raft_components("/tmp/test_broadcast_vote_requests_case5", None, false); - let test_context = setup().await; - let my_last_log_index = 1; - let my_last_log_term = 3; - - let transport_mock: TROF = MockTransport::new(); - - let e = test_context - .election_handler - .broadcast_vote_requests( - my_last_log_term, - Arc::new(mock_membership()), - &Arc::new(test_context.raft_log_mock), - &Arc::new(transport_mock), - &ctx.arc_node_config, - ) - .await - .unwrap_err(); - - if let Error::Consensus(ConsensusError::Election(ElectionError::LogConflict { - index, - expected_term, - actual_term, - })) = e - { - assert_eq!(index, my_last_log_index); - assert_eq!(expected_term, my_last_log_term); - assert_eq!(actual_term, my_last_log_term); - } -} - -/// # Case 1: Test if vote request is legal -/// -/// ## Validation criterias: -/// 1. Switch back to Follower -/// 2. Returns with Ok(state_update) -/// - step_to_follower = true -/// - voted_for_option = Some(x) -/// - term_update = Some(y) -#[tokio::test] -#[traced_test] -async fn test_handle_vote_request_case1() { - let (_graceful_tx, _graceful_rx) = watch::channel(()); - let mut test_context = setup().await; - test_context - .raft_log_mock - .expect_last_log_id() - .times(1) - .returning(|| Some(LogId { index: 1, term: 1 })); - - let current_term = 1; - let last_log_index = 1; - let last_log_term = 1; - - let request_term = current_term + 1; - let vote_request = VoteRequest { - term: request_term, - candidate_id: 1, - last_log_index: last_log_index + 1, - last_log_term, - }; - let voted_for_option = None; - assert!( - test_context - .election_handler - .handle_vote_request( - vote_request, - current_term, - voted_for_option, - &Arc::new(test_context.raft_log_mock), - ) - .await - .is_ok_and(move |state_update| state_update.new_voted_for.is_some() - && state_update.term_update.unwrap() == request_term) - ); -} - -/// # Case 2: Test if vote request is illegal -/// (current_term >= VoteRequest Term) -/// -/// ## Validation criterias: -/// 1. role_rx.try_recv with None -/// 2. Returns with Ok(None) -#[tokio::test] -#[traced_test] -async fn test_handle_vote_request_case2() { - let (_graceful_tx, _graceful_rx) = watch::channel(()); - let mut test_context = setup().await; - test_context - .raft_log_mock - .expect_last_log_id() - .times(1) - .returning(|| Some(LogId { index: 1, term: 1 })); - - let current_term = 10; - let last_log_index = 1; - let last_log_term = 1; - - let vote_request = VoteRequest { - term: current_term - 1, - candidate_id: 1, - last_log_index: last_log_index + 1, - last_log_term, - }; - let voted_for_option = None; - - let state_update = test_context - .election_handler - .handle_vote_request( - vote_request, - current_term, - voted_for_option, - &Arc::new(test_context.raft_log_mock), - ) - .await - .unwrap(); - assert!(state_update.new_voted_for.is_none()); -} - -/// Case 1.1: Term and local log index compare -/// - current_term >= VoteRequest Term -/// -/// Criterias: -/// - false -#[tokio::test] -#[traced_test] -async fn test_check_vote_request_is_legal_case_1_1() { - // 1. Prepare RaftContext mock - let context = setup_raft_components( - "/tmp/test_check_vote_request_is_legal_case_1_1", - None, - false, - ); - let election_controller = context.election_handler; - - let vote_request = VoteRequest { - term: 1, - candidate_id: 1, - last_log_index: 1, - last_log_term: 1, - }; - let current_term = 1; - let last_log_index = 1; - let last_log_term = 1; - let voted_for_id = 1; - let voted_for_term = 1; - - assert!(!election_controller.check_vote_request_is_legal( - &vote_request, - current_term, - last_log_index, - last_log_term, - Some(VotedFor { - voted_for_id, - voted_for_term - }) - )); - let current_term = 2; - assert!(!election_controller.check_vote_request_is_legal( - &vote_request, - current_term, - last_log_index, - last_log_term, - Some(VotedFor { - voted_for_id, - voted_for_term - }) - )); -} - -/// Case 1.2: Term and local log index compare -/// - current_term <= VoteRequest Term -/// - request.last_log_term <= last_log_term -/// -/// Criterias: -/// - false -#[tokio::test] -#[traced_test] -async fn test_check_vote_request_is_legal_case_1_2() { - // 1. Prepare RaftContext mock - let context = setup_raft_components( - "/tmp/test_check_vote_request_is_legal_case_1_2", - None, - false, - ); - let election_controller = context.election_handler; - let current_term = 1; - - let vote_request = VoteRequest { - term: current_term, - candidate_id: 1, - last_log_index: 1, - last_log_term: 1, - }; - let last_log_index = 1; - let voted_for_id = 1; - let voted_for_term = 1; - - let last_log_term = 2; - assert!(!election_controller.check_vote_request_is_legal( - &vote_request, - current_term, - last_log_index, - last_log_term, - Some(VotedFor { - voted_for_id, - voted_for_term - }) - )); - - let last_log_term = 1; - assert!(!election_controller.check_vote_request_is_legal( - &vote_request, - current_term, - last_log_index, - last_log_term, - Some(VotedFor { - voted_for_id, - voted_for_term - }) - )); -} - -/// Case 1.3: Term and local log index compare -/// - current_term <= VoteRequest Term -/// - request.last_log_term = last_log_term -/// - request.last_log_index > last_log_index -/// -/// Criterias: -/// - true -#[tokio::test] -#[traced_test] -async fn test_check_vote_request_is_legal_case_1_3() { - // 1. Prepare RaftContext mock - let context = setup_raft_components( - "/tmp/test_check_vote_request_is_legal_case_1_3", - None, - false, - ); - let election_controller = context.election_handler; - let current_term = 1; - let last_log_index = 1; - let last_log_term = 1; - - let vote_request = VoteRequest { - term: current_term, - candidate_id: 1, - last_log_index: last_log_index + 1, - last_log_term, - }; - assert!(election_controller.check_vote_request_is_legal( - &vote_request, - current_term, - last_log_index, - last_log_term, - None, - )); -} - -/// Case 1.4: Term and local log index compare -/// - current_term <= VoteRequest Term -/// - request.last_log_term = last_log_term -/// - request.last_log_index < last_log_index -/// -/// Criterias: -/// - false -#[tokio::test] -#[traced_test] -async fn test_check_vote_request_is_legal_case_1_4() { - // 1. Prepare RaftContext mock - let context = setup_raft_components( - "/tmp/test_check_vote_request_is_legal_case_1_4", - None, - false, - ); - let election_controller = context.election_handler; - let current_term = 1; - - let vote_request = VoteRequest { - term: current_term, - candidate_id: 1, - last_log_index: 1, - last_log_term: 1, - }; - let last_log_index = 2; - let voted_for_id = 1; - let voted_for_term = 1; - - let last_log_term = 1; - assert!(!election_controller.check_vote_request_is_legal( - &vote_request, - current_term, - last_log_index, - last_log_term, - Some(VotedFor { - voted_for_id, - voted_for_term - }) - )); -} - -/// Case 2.1: granted_vote checking -/// - current_term <= VoteRequest Term -/// - request.last_log_term = last_log_term -/// - request.last_log_index >= last_log_index -/// - current node has granted vote -/// -/// Criterias: -/// - false -#[tokio::test] -#[traced_test] -async fn test_check_vote_request_is_legal_case_2_1() { - // 1. Prepare RaftContext mock - let context = setup_raft_components( - "/tmp/test_check_vote_request_is_legal_case_2_1", - None, - false, - ); - let election_controller = context.election_handler; - let current_term = 1; - - let vote_request = VoteRequest { - term: current_term, - candidate_id: 1, - last_log_index: 3, - last_log_term: 1, - }; - let last_log_index = 2; - let last_log_term = 1; - - let voted_for_id = 3; - let voted_for_term = 1; - - assert!(!election_controller.check_vote_request_is_legal( - &vote_request, - current_term, - last_log_index, - last_log_term, - Some(VotedFor { - voted_for_id, - voted_for_term - }) - )); -} - -/// Case 2.2: granted_vote checking -/// - current_term <= VoteRequest Term -/// - request.last_log_term = last_log_term -/// - request.last_log_index >= last_log_index -/// - current node has granted vote, and the granted vote term is bigger than the request one. -/// -/// Criterias: -/// - false -#[tokio::test] -#[traced_test] -async fn test_check_vote_request_is_legal_case_2_2() { - // 1. Prepare RaftContext mock - let context = setup_raft_components( - "/tmp/test_check_vote_request_is_legal_case_2_2", - None, - false, - ); - let election_controller = context.election_handler; - let current_term = 1; - - let vote_request = VoteRequest { - term: current_term, - candidate_id: 1, - last_log_index: 3, - last_log_term: 1, - }; - let last_log_index = 2; - let last_log_term = 1; - - let voted_for_id = 1; - let voted_for_term = 10; - - assert!(!election_controller.check_vote_request_is_legal( - &vote_request, - current_term, - last_log_index, - last_log_term, - Some(VotedFor { - voted_for_id, - voted_for_term - }) - )); -} -/// Case 2.3: granted_vote checking -/// - current_term <= VoteRequest Term -/// - request.last_log_term = last_log_term -/// - request.last_log_index >= last_log_index -/// - current node has granted vote, and the granted vote term is bigger than the request one. -/// -/// Criterias: -/// - true -#[tokio::test] -#[traced_test] -async fn test_check_vote_request_is_legal_case_2_3() { - // 1. Prepare RaftContext mock - let context = setup_raft_components( - "/tmp/test_check_vote_request_is_legal_case_2_3", - None, - false, - ); - let election_controller = context.election_handler; - let current_term = 10; - - let vote_request = VoteRequest { - term: current_term, - candidate_id: 1, - last_log_index: 3, - last_log_term: 1, - }; - let last_log_index = 2; - let last_log_term = 1; - - let voted_for_id = 1; - let voted_for_term = 1; - - assert!(election_controller.check_vote_request_is_legal( - &vote_request, - current_term, - last_log_index, - last_log_term, - Some(VotedFor { - voted_for_id, - voted_for_term - }) - )); -} diff --git a/d-engine-core/src/election/mod.rs b/d-engine-core/src/election/mod.rs index 8094345b..d2af994d 100644 --- a/d-engine-core/src/election/mod.rs +++ b/d-engine-core/src/election/mod.rs @@ -7,9 +7,6 @@ mod election_handler; pub use election_handler::*; -// #[cfg(test)] -// mod election_handler_test; - use std::sync::Arc; #[cfg(any(test, feature = "test-utils"))] diff --git a/d-engine-core/src/errors_test.rs b/d-engine-core/src/errors_test.rs new file mode 100644 index 00000000..1d01102d --- /dev/null +++ b/d-engine-core/src/errors_test.rs @@ -0,0 +1,482 @@ +use std::{path::PathBuf, time::Duration}; + +use super::*; + +#[test] +fn test_error_fatal() { + let err = Error::Fatal("critical failure".to_string()); + assert_eq!(err.to_string(), "Fatal error: critical failure"); +} + +#[test] +fn test_consensus_error_role_violation() { + let err = ConsensusError::RoleViolation { + current_role: "Follower", + required_role: "Leader", + context: "Cannot accept write requests".to_string(), + }; + let msg = err.to_string(); + assert!(msg.contains("Follower")); + assert!(msg.contains("Leader")); +} + +#[test] +fn test_state_transition_error_not_enough_votes() { + let err = StateTransitionError::NotEnoughVotes; + assert_eq!(err.to_string(), "Not enough votes to transition to leader."); +} + +#[test] +fn test_state_transition_error_invalid_transition() { + let err = StateTransitionError::InvalidTransition; + assert_eq!(err.to_string(), "Invalid state transition."); +} + +#[test] +fn test_state_transition_error_lock_error() { + let err = StateTransitionError::LockError; + assert_eq!(err.to_string(), "Lock error."); +} + +#[test] +fn test_network_error_service_unavailable() { + let err = NetworkError::ServiceUnavailable("node down".to_string()); + assert_eq!(err.to_string(), "Service unavailable: node down"); +} + +#[test] +fn test_network_error_timeout() { + let err = NetworkError::Timeout { + node_id: 42, + duration: Duration::from_secs(5), + }; + let msg = err.to_string(); + assert!(msg.contains("42")); + assert!(msg.contains("5s")); +} + +#[test] +fn test_network_error_empty_peer_list() { + let err = NetworkError::EmptyPeerList { + request_type: "AppendEntries", + }; + let msg = err.to_string(); + assert!(msg.contains("AppendEntries")); +} + +#[test] +fn test_network_error_signal_send_failed() { + let err = NetworkError::SingalSendFailed("channel closed".to_string()); + let msg = err.to_string(); + assert!(msg.contains("channel closed")); +} + +#[test] +fn test_storage_error_data_corruption() { + let err = StorageError::DataCorruption { + location: "raft_log_entry_42".to_string(), + }; + let msg = err.to_string(); + assert!(msg.contains("raft_log_entry_42")); +} + +#[test] +fn test_storage_error_path_error() { + let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found"); + let err = StorageError::PathError { + path: PathBuf::from("/data/raft.db"), + source: io_err, + }; + let msg = err.to_string(); + assert!(msg.contains("/data/raft.db")); +} + +#[test] +fn test_id_allocation_error_overflow() { + let err = IdAllocationError::Overflow { + start: 100, + end: 50, + }; + let msg = err.to_string(); + assert!(msg.contains("100")); + assert!(msg.contains("50")); +} + +#[test] +fn test_id_allocation_error_invalid_range() { + let err = IdAllocationError::InvalidRange { start: 10, end: 5 }; + let msg = err.to_string(); + assert!(msg.contains("10")); + assert!(msg.contains("5")); +} + +#[test] +fn test_id_allocation_error_no_ids_available() { + let err = IdAllocationError::NoIdsAvailable; + let msg = err.to_string(); + assert!(msg.contains("IDs") || msg.contains("available")); +} + +#[test] +fn test_file_error_not_found() { + let err = FileError::NotFound("/path/to/file".to_string()); + let msg = err.to_string(); + assert!(msg.contains("/path/to/file")); +} + +#[test] +fn test_file_error_is_directory() { + let err = FileError::IsDirectory("/path/to/dir".to_string()); + let msg = err.to_string(); + assert!(msg.contains("/path/to/dir")); +} + +#[test] +fn test_file_error_permission_denied() { + let err = FileError::PermissionDenied("/restricted/file".to_string()); + let msg = err.to_string(); + assert!(msg.contains("/restricted/file")); +} + +#[test] +fn test_file_error_invalid_gzip_header() { + let err = FileError::InvalidGzipHeader("header mismatch".to_string()); + let msg = err.to_string(); + assert!(msg.contains("header mismatch")); +} + +#[test] +fn test_file_error_too_small() { + let err = FileError::TooSmall(42); + let msg = err.to_string(); + assert!(msg.contains("42")); +} + +#[test] +fn test_convert_error_invalid_length() { + let err = ConvertError::InvalidLength(4); + let msg = err.to_string(); + assert!(msg.contains("4")); + assert!(msg.contains("8")); +} + +#[test] +fn test_convert_error_conversion_failure() { + let err = ConvertError::ConversionFailure("Cannot convert to u64".to_string()); + let msg = err.to_string(); + assert!(msg.contains("Cannot convert to u64")); +} + +#[test] +fn test_write_send_error_not_leader() { + let err = WriteSendError::NotLeader; + let msg = err.to_string(); + assert!(msg.contains("leader")); +} + +#[test] +fn test_write_send_error_payload_exceeded() { + let err = WriteSendError::PayloadExceeded; + let msg = err.to_string(); + assert!(msg.contains("large") || msg.contains("Payload")); +} + +#[test] +fn test_write_send_error_unreachable() { + let err = WriteSendError::Unreachable; + let msg = err.to_string(); + assert!(msg.contains("unreachable")); +} + +#[test] +fn test_system_error_server_unavailable() { + let err = SystemError::ServerUnavailable; + let msg = err.to_string(); + assert!(msg.contains("unavailable") || msg.contains("server")); +} + +#[test] +fn test_election_error_higher_term() { + let err = ElectionError::HigherTerm(10); + let msg = err.to_string(); + assert!(msg.contains("10")); +} + +#[test] +fn test_election_error_term_conflict() { + let err = ElectionError::TermConflict { + current: 5, + received: 3, + }; + let msg = err.to_string(); + assert!(msg.contains("5")); + assert!(msg.contains("3")); +} + +#[test] +fn test_election_error_log_conflict() { + let err = ElectionError::LogConflict { + index: 100, + expected_term: 5, + actual_term: 4, + }; + let msg = err.to_string(); + assert!(msg.contains("100")); + assert!(msg.contains("5")); + assert!(msg.contains("4")); +} + +#[test] +fn test_election_error_quorum_failure() { + let err = ElectionError::QuorumFailure { + required: 3, + succeed: 2, + }; + let msg = err.to_string(); + assert!(msg.contains("3")); + assert!(msg.contains("2")); +} + +#[test] +fn test_election_error_no_voting_member_found() { + let err = ElectionError::NoVotingMemberFound { candidate_id: 42 }; + let msg = err.to_string(); + assert!(msg.contains("42")); +} + +#[test] +fn test_replication_error_higher_term() { + let err = ReplicationError::HigherTerm(15); + let msg = err.to_string(); + assert!(msg.contains("15")); +} + +#[test] +fn test_replication_error_quorum_not_reached() { + let err = ReplicationError::QuorumNotReached; + let msg = err.to_string(); + assert!(msg.contains("Quorum") && msg.contains("replication")); +} + +#[test] +fn test_replication_error_node_unreachable() { + let err = ReplicationError::NodeUnreachable { node_id: 123 }; + let msg = err.to_string(); + assert!(msg.contains("123")); +} + +#[test] +fn test_replication_error_log_conflict() { + let err = ReplicationError::LogConflict { + index: 50, + expected_term: 3, + actual_term: 2, + }; + let msg = err.to_string(); + assert!(msg.contains("50")); + assert!(msg.contains("3")); + assert!(msg.contains("2")); +} + +#[test] +fn test_replication_error_not_leader() { + let err = ReplicationError::NotLeader { leader_id: Some(1) }; + let msg = err.to_string(); + assert!(msg.contains("1")); +} + +#[test] +fn test_membership_error_not_leader() { + let err = MembershipError::NotLeader; + let msg = err.to_string(); + assert!(msg.contains("leader")); +} + +#[test] +fn test_membership_error_no_leader_found() { + let err = MembershipError::NoLeaderFound; + let msg = err.to_string(); + assert!(msg.contains("leader")); +} + +#[test] +fn test_membership_error_node_already_exists() { + let err = MembershipError::NodeAlreadyExists(42); + let msg = err.to_string(); + assert!(msg.contains("42")); +} + +#[test] +fn test_membership_error_invalid_promotion() { + let err = MembershipError::InvalidPromotion { + node_id: 5, + role: 1, // Use i32 instead of String + }; + let msg = err.to_string(); + assert!(msg.contains("5")); + assert!(msg.contains("1")); +} + +#[test] +fn test_membership_error_remove_node_is_leader() { + let err = MembershipError::RemoveNodeIsLeader(3); + let msg = err.to_string(); + assert!(msg.contains("3")); +} + +#[test] +fn test_membership_error_no_metadata_found() { + let err = MembershipError::NoMetadataFoundForNode { node_id: 99 }; + let msg = err.to_string(); + assert!(msg.contains("99")); +} + +#[test] +fn test_snapshot_error_rejected() { + let err = SnapshotError::Rejected { last_chunk: 42 }; + let msg = err.to_string(); + assert!(msg.contains("42")); +} + +#[test] +fn test_snapshot_error_checksum_mismatch() { + let err = SnapshotError::ChecksumMismatch; + let msg = err.to_string(); + assert!(msg.contains("checksum") && msg.contains("mismatch")); +} + +#[test] +fn test_snapshot_error_invalid_snapshot() { + let err = SnapshotError::InvalidSnapshot; + let msg = err.to_string(); + assert!(msg.contains("snapshot") || msg.contains("Invalid")); +} + +#[test] +fn test_snapshot_error_incomplete_snapshot() { + let err = SnapshotError::IncompleteSnapshot; + let msg = err.to_string(); + assert!(msg.contains("snapshot") || msg.contains("Incomplete")); +} + +// Test From trait implementations for error conversions +#[test] +fn test_from_network_error_to_error() { + let network_err = NetworkError::ServiceUnavailable("test".to_string()); + let err: Error = network_err.into(); + assert!(matches!(err, Error::System(SystemError::Network(_)))); +} + +#[test] +fn test_from_storage_error_to_error() { + let storage_err = StorageError::LogStorage("test error".to_string()); + let err: Error = storage_err.into(); + assert!(matches!(err, Error::System(SystemError::Storage(_)))); +} + +#[test] +fn test_from_state_transition_error_to_error() { + let state_err = StateTransitionError::NotEnoughVotes; + let err: Error = state_err.into(); + assert!(matches!( + err, + Error::Consensus(ConsensusError::StateTransition(_)) + )); +} + +#[test] +fn test_from_election_error_to_error() { + let election_err = ElectionError::Failed("election timeout".to_string()); + let err: Error = election_err.into(); + assert!(matches!(err, Error::Consensus(ConsensusError::Election(_)))); +} + +#[test] +fn test_from_replication_error_to_error() { + let replication_err = ReplicationError::QuorumNotReached; + let err: Error = replication_err.into(); + assert!(matches!( + err, + Error::Consensus(ConsensusError::Replication(_)) + )); +} + +#[test] +fn test_from_membership_error_to_error() { + let membership_err = MembershipError::NotLeader; + let err: Error = membership_err.into(); + assert!(matches!( + err, + Error::Consensus(ConsensusError::Membership(_)) + )); +} + +#[test] +fn test_from_snapshot_error_to_error() { + let snapshot_err = SnapshotError::InvalidSnapshot; + let err: Error = snapshot_err.into(); + assert!(matches!(err, Error::Consensus(ConsensusError::Snapshot(_)))); +} + +#[test] +fn test_from_io_error_to_error() { + let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "test"); + let err: Error = io_err.into(); + assert!(matches!(err, Error::System(SystemError::Storage(_)))); +} + +#[test] +fn test_from_id_allocation_error_to_error() { + let id_err = IdAllocationError::NoIdsAvailable; + let err: Error = id_err.into(); + assert!(matches!(err, Error::System(SystemError::Storage(_)))); +} + +#[test] +fn test_error_display_formatting() { + let err = Error::Fatal("system failure".to_string()); + let display = format!("{err}"); + assert_eq!(display, "Fatal error: system failure"); +} + +#[test] +fn test_consensus_error_nesting() { + let state_err = StateTransitionError::InvalidTransition; + let consensus_err: ConsensusError = state_err.into(); + let top_err: Error = consensus_err.into(); + + assert!(matches!( + top_err, + Error::Consensus(ConsensusError::StateTransition( + StateTransitionError::InvalidTransition + )) + )); +} + +#[test] +fn test_network_error_connect_error() { + let err = NetworkError::ConnectError("connection refused".to_string()); + let msg = err.to_string(); + assert!(msg.contains("connection refused")); +} + +#[test] +fn test_network_error_invalid_uri() { + let err = NetworkError::InvalidURI("malformed://uri".to_string()); + let msg = err.to_string(); + assert!(msg.contains("malformed://uri")); +} + +#[test] +fn test_system_error_general_server() { + let err = SystemError::GeneralServer("server error".to_string()); + let msg = err.to_string(); + assert!(msg.contains("server error")); +} + +#[test] +fn test_system_error_node_start_failed_with_message() { + let err = SystemError::NodeStartFailed("initialization failed".to_string()); + let msg = err.to_string(); + assert!(msg.contains("initialization failed")); +} diff --git a/d-engine-core/src/lib.rs b/d-engine-core/src/lib.rs index b67a0029..b8247e53 100644 --- a/d-engine-core/src/lib.rs +++ b/d-engine-core/src/lib.rs @@ -46,6 +46,8 @@ pub use type_config::*; #[cfg(test)] mod maybe_clone_oneshot_test; +#[cfg(test)] +mod errors_test; #[cfg(test)] mod raft_oneshot_test; diff --git a/d-engine-core/src/network/backgroup_snapshot_transfer_test.rs b/d-engine-core/src/network/backgroup_snapshot_transfer_test.rs index 8f62d953..00b9aada 100644 --- a/d-engine-core/src/network/backgroup_snapshot_transfer_test.rs +++ b/d-engine-core/src/network/backgroup_snapshot_transfer_test.rs @@ -16,8 +16,8 @@ use crate::NetworkError; use crate::SnapshotConfig; use crate::SnapshotError; use crate::StorageError; -use crate::crate_test_snapshot_stream; use crate::create_test_chunk; +use crate::create_test_snapshot_stream; use d_engine_proto::server::storage::SnapshotChunk; use d_engine_proto::server::storage::SnapshotResponse; @@ -39,7 +39,7 @@ fn create_snapshot_stream( }) .collect(); - let stream = crate_test_snapshot_stream(chunks); + let stream = create_test_snapshot_stream(chunks); Box::pin( stream.map(|item| item.map_err(|s| NetworkError::TonicStatusError(Box::new(s)).into())), ) diff --git a/d-engine-core/src/raft.rs b/d-engine-core/src/raft.rs index c2bae693..f68ddd52 100644 --- a/d-engine-core/src/raft.rs +++ b/d-engine-core/src/raft.rs @@ -37,11 +37,11 @@ where pub ctx: RaftContext, // Network & Storage events - pub event_tx: mpsc::Sender, + event_tx: mpsc::Sender, event_rx: mpsc::Receiver, // Timer - pub role_tx: mpsc::UnboundedSender, + role_tx: mpsc::UnboundedSender, role_rx: mpsc::UnboundedReceiver, // For business logic to apply logs into state machine @@ -59,11 +59,33 @@ where } pub struct SignalParams { - pub role_tx: mpsc::UnboundedSender, - pub role_rx: mpsc::UnboundedReceiver, - pub event_tx: mpsc::Sender, - pub event_rx: mpsc::Receiver, - pub shutdown_signal: watch::Receiver<()>, + pub(crate) role_tx: mpsc::UnboundedSender, + pub(crate) role_rx: mpsc::UnboundedReceiver, + pub(crate) event_tx: mpsc::Sender, + pub(crate) event_rx: mpsc::Receiver, + pub(crate) shutdown_signal: watch::Receiver<()>, +} + +impl SignalParams { + /// Creates a new SignalParams with the provided channels. + /// + /// This is the only way to construct SignalParams from outside d-engine-core, + /// ensuring controlled initialization of the internal communication channels. + pub fn new( + role_tx: mpsc::UnboundedSender, + role_rx: mpsc::UnboundedReceiver, + event_tx: mpsc::Sender, + event_rx: mpsc::Receiver, + shutdown_signal: watch::Receiver<()>, + ) -> Self { + Self { + role_tx, + role_rx, + event_tx, + event_rx, + shutdown_signal, + } + } } impl Raft @@ -376,6 +398,30 @@ where ) { self.role = role } + + /// Returns a cloned event sender for external use. + /// + /// This provides controlled access to send validated RaftEvents to the Raft core. + /// Events sent through this sender are still processed through the normal validation + /// pipeline in the main event loop. + /// + /// # Security Note + /// While this provides access to the event channel, all events are still validated + /// by the Raft state machine before being applied. The event handler in `handle_raft_event` + /// performs necessary checks based on current term, role, and state. + pub fn event_sender(&self) -> mpsc::Sender { + self.event_tx.clone() + } + + /// Returns a cloned role event sender for internal use. + /// + /// # Warning + /// This is primarily for internal components that need to trigger role transitions. + /// External callers should not use this unless they understand the Raft protocol deeply. + #[doc(hidden)] + pub fn role_event_sender(&self) -> mpsc::UnboundedSender { + self.role_tx.clone() + } } impl Drop for Raft diff --git a/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs index 9c5d257b..414184fd 100644 --- a/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs +++ b/d-engine-core/src/state_machine_handler/default_state_machine_handler_test.rs @@ -27,9 +27,9 @@ use crate::MockStateMachine; use crate::MockTypeConfig; use crate::SnapshotError; use crate::StorageError; -use crate::test_utils::crate_test_snapshot_stream; use crate::test_utils::create_test_chunk; use crate::test_utils::create_test_compressed_snapshot; +use crate::test_utils::create_test_snapshot_stream; use crate::test_utils::snapshot_config; use d_engine_proto::common::Entry; use d_engine_proto::common::LogId; @@ -454,7 +454,7 @@ async fn test_apply_snapshot_stream_from_leader_case2() { }; chunks.push(chunk); - let streaming_request = crate_test_snapshot_stream(chunks); + let streaming_request = create_test_snapshot_stream(chunks); let (ack_tx, mut ack_rx) = mpsc::channel::(1); // Spawn the handler in a separate task to prevent deadlock @@ -493,7 +493,7 @@ async fn test_apply_snapshot_stream_from_leader_case3() { // Create ACK channel let (ack_tx, mut ack_rx) = mpsc::channel::(1); - let stream = crate_test_snapshot_stream(vec![bad_chunk]); + let stream = create_test_snapshot_stream(vec![bad_chunk]); let handler_task = tokio::spawn({ let config = snapshot_config(temp_path.to_path_buf()); @@ -530,7 +530,7 @@ async fn test_apply_snapshot_stream_from_leader_case4() { ]; let (ack_tx, mut ack_rx) = mpsc::channel::(1); - let stream = crate_test_snapshot_stream(chunks); + let stream = create_test_snapshot_stream(chunks); let handler_task = tokio::spawn({ let config = snapshot_config(temp_path.to_path_buf()); @@ -572,7 +572,7 @@ async fn test_apply_snapshot_stream_from_leader_case5() { TEST_LEADER_ID, 2, )]; - let stream = crate_test_snapshot_stream(chunks); + let stream = create_test_snapshot_stream(chunks); let (ack_tx, mut ack_rx) = mpsc::channel::(1); let handler_task = tokio::spawn({ @@ -608,7 +608,7 @@ async fn test_apply_snapshot_stream_from_leader_case6() { invalid_chunk.metadata = None; let (ack_tx, mut ack_rx) = mpsc::channel::(1); - let stream = crate_test_snapshot_stream(vec![invalid_chunk]); + let stream = create_test_snapshot_stream(vec![invalid_chunk]); let handler_task = tokio::spawn({ let config = snapshot_config(temp_path.to_path_buf()); @@ -680,7 +680,7 @@ async fn test_apply_snapshot_stream_from_leader_case7() { chunks.push(chunk); } - let streaming_request = crate_test_snapshot_stream(chunks); + let streaming_request = create_test_snapshot_stream(chunks); let (ack_tx, mut ack_rx) = mpsc::channel::(1); // Spawn the handler in a separate task @@ -1906,7 +1906,7 @@ async fn test_apply_snapshot_stream_from_leader_decompresses_before_apply() { chunk_checksum: Bytes::from(chunk_checksum), }; - let streaming_request = crate_test_snapshot_stream(vec![chunk]); + let streaming_request = create_test_snapshot_stream(vec![chunk]); let (ack_tx, mut ack_rx) = mpsc::channel::(1); let handler_task = tokio::spawn({ diff --git a/d-engine-core/src/test_utils/mock/mock_rpc.rs b/d-engine-core/src/test_utils/mock/mock_rpc.rs index e5c4b22f..ad9dbed0 100644 --- a/d-engine-core/src/test_utils/mock/mock_rpc.rs +++ b/d-engine-core/src/test_utils/mock/mock_rpc.rs @@ -4,7 +4,7 @@ use tokio_stream::StreamExt; use tonic::Streaming; use tracing::trace; -use crate::test_utils::crate_test_snapshot_stream; +use crate::test_utils::create_test_snapshot_stream; use d_engine_proto::client::ClientReadRequest; use d_engine_proto::client::ClientResponse; use d_engine_proto::client::ClientWriteRequest; @@ -132,9 +132,7 @@ impl ClusterManagementService for MockRpcService { match &self.expected_join_cluster_response { Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), Some(Err(status)) => Err(status.clone()), - None => Err(tonic::Status::unknown( - "No mock get_cluster_metadata response set", - )), + None => Err(tonic::Status::unknown("No mock join_cluster response set")), } } @@ -192,7 +190,7 @@ impl SnapshotService for MockRpcService { match &self.expected_stream_snapshot_response { Some(Ok(response)) => { let streaming: Self::StreamSnapshotStream = - crate_test_snapshot_stream(vec![response.clone()]); + create_test_snapshot_stream(vec![response.clone()]); Ok(tonic::Response::new(streaming)) } Some(Err(status)) => Err(status.clone()), diff --git a/d-engine-core/src/test_utils/snapshot.rs b/d-engine-core/src/test_utils/snapshot.rs index 1cd5c3c1..dd7b0ddf 100644 --- a/d-engine-core/src/test_utils/snapshot.rs +++ b/d-engine-core/src/test_utils/snapshot.rs @@ -22,7 +22,7 @@ use d_engine_proto::common::LogId; use d_engine_proto::server::storage::SnapshotChunk; use d_engine_proto::server::storage::SnapshotMetadata; -pub fn crate_test_snapshot_stream(chunks: Vec) -> tonic::Streaming +pub fn create_test_snapshot_stream(chunks: Vec) -> tonic::Streaming where T: prost::Message + Default + 'static, { @@ -102,7 +102,7 @@ pub async fn create_test_compressed_snapshot() -> (Vec, SnapshotMetadata) { } #[allow(unused)] -pub(crate) fn crate_test_snapshot_stream_from_receiver( +pub(crate) fn create_test_snapshot_stream_from_receiver( receiver: mpsc::Receiver ) -> tonic::Streaming where diff --git a/d-engine-server/src/network/grpc/grpc_transport_test.rs b/d-engine-server/src/network/grpc/grpc_transport_test.rs index a9b90723..1e7bc51d 100644 --- a/d-engine-server/src/network/grpc/grpc_transport_test.rs +++ b/d-engine-server/src/network/grpc/grpc_transport_test.rs @@ -21,8 +21,8 @@ use d_engine_core::RaftNodeConfig; use d_engine_core::RetryPolicies; use d_engine_core::SystemError; use d_engine_core::Transport; -use d_engine_core::crate_test_snapshot_stream; use d_engine_core::create_test_chunk; +use d_engine_core::create_test_snapshot_stream; use d_engine_core::test_utils::MockNode; use d_engine_core::test_utils::node_config; use d_engine_proto::common::LogId; @@ -1060,7 +1060,7 @@ fn create_failing_stream(fail_at: usize) -> BoxStream<'static, Result> { node_id, raft_core: Arc::new(Mutex::new(raft_core)), diff --git a/d-engine-server/src/node/mod.rs b/d-engine-server/src/node/mod.rs index fe14244a..e9a67965 100644 --- a/d-engine-server/src/node/mod.rs +++ b/d-engine-server/src/node/mod.rs @@ -176,7 +176,7 @@ where /// Create a Node from a pre-built Raft instance /// This method is designed to support testing and external builders pub fn from_raft(raft: Raft) -> Self { - let event_tx = raft.event_tx.clone(); + let event_tx = raft.event_sender(); let node_config = raft.ctx.node_config(); let membership = raft.ctx.membership(); let node_id = raft.node_id; diff --git a/d-engine-server/src/test_utils/mock/mock_node_builder.rs b/d-engine-server/src/test_utils/mock/mock_node_builder.rs index c1c49318..11d140c9 100644 --- a/d-engine-server/src/test_utils/mock/mock_node_builder.rs +++ b/d-engine-server/src/test_utils/mock/mock_node_builder.rs @@ -283,13 +283,7 @@ impl MockBuilder { purge_executor: Arc::new(purge_executor), }, membership, - SignalParams { - role_tx, - role_rx, - event_tx, - event_rx, - shutdown_signal: self.shutdown_signal, - }, + SignalParams::new(role_tx, role_rx, event_tx, event_rx, self.shutdown_signal), arc_node_config.clone(), ) } @@ -299,7 +293,7 @@ impl MockBuilder { /// Wraps the Raft instance in a Node container for integration testing. pub fn build_node(self) -> Node { let raft = self.build_raft(); - let event_tx = raft.event_tx.clone(); + let event_tx = raft.event_sender(); let node_config = raft.ctx.node_config.clone(); let membership = raft.ctx.membership.clone(); Node:: { @@ -321,7 +315,7 @@ impl MockBuilder { let node_config_option = self.node_config.clone(); let raft = self.build_raft(); - let event_tx = raft.event_tx.clone(); + let event_tx = raft.event_sender(); let node_config = node_config_option.unwrap_or_else(|| { RaftNodeConfig::new().expect("Should succeed to init RaftNodeConfig") }); diff --git a/d-engine-server/src/test_utils/mock/mock_rpc_services.rs b/d-engine-server/src/test_utils/mock/mock_rpc_services.rs deleted file mode 100644 index 909c58c6..00000000 --- a/d-engine-server/src/test_utils/mock/mock_rpc_services.rs +++ /dev/null @@ -1,533 +0,0 @@ -use d_engine_core::Result; -use d_engine_core::StorageError; -use d_engine_proto::client::ClientReadRequest; -use d_engine_proto::client::ClientResponse; -use d_engine_proto::client::ClientWriteRequest; -use d_engine_proto::client::raft_client_service_server::RaftClientService; -use d_engine_proto::client::raft_client_service_server::RaftClientServiceServer; -use d_engine_proto::common::NodeRole::Leader; -use d_engine_proto::common::NodeStatus; -use d_engine_proto::server::cluster::ClusterConfChangeRequest; -use d_engine_proto::server::cluster::JoinRequest; -use d_engine_proto::server::cluster::LeaderDiscoveryRequest; -use d_engine_proto::server::cluster::MetadataRequest; -use d_engine_proto::server::cluster::NodeMeta; -use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementService; -use d_engine_proto::server::cluster::cluster_management_service_server::ClusterManagementServiceServer; -use d_engine_proto::server::cluster::{ - ClusterConfUpdateResponse, ClusterMembership, JoinResponse, LeaderDiscoveryResponse, -}; -use d_engine_proto::server::election::VoteRequest; -use d_engine_proto::server::election::VoteResponse; -use d_engine_proto::server::election::raft_election_service_server::RaftElectionService; -use d_engine_proto::server::election::raft_election_service_server::RaftElectionServiceServer; -use d_engine_proto::server::replication::AppendEntriesRequest; -use d_engine_proto::server::replication::AppendEntriesResponse; -use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationService; -use d_engine_proto::server::replication::raft_replication_service_server::RaftReplicationServiceServer; -use d_engine_proto::server::storage::PurgeLogRequest; -use d_engine_proto::server::storage::SnapshotAck; -use d_engine_proto::server::storage::snapshot_service_server::SnapshotService; -use d_engine_proto::server::storage::snapshot_service_server::SnapshotServiceServer; -use d_engine_proto::server::storage::{PurgeLogResponse, SnapshotChunk, SnapshotResponse}; -use std::net::SocketAddr; -use std::sync::Arc; -use tokio::net::TcpListener; -use tokio::sync::oneshot; -use tokio_stream::StreamExt; -use tonic::Status; -use tonic::Streaming; -use tonic::codec::CompressionEncoding; -use tonic::transport::Channel; -use tonic_health::server::health_reporter; -use tracing::trace; -use tracing::{debug, info}; - -use d_engine_core::crate_test_snapshot_stream; - -#[derive(Clone, Default)] -pub struct MockRpcService { - pub server_port: Option, - // Expected responses for each method - pub expected_vote_response: Option>, - pub expected_append_entries_response: - Option>, - pub expected_update_cluster_conf_response: - Option>, - pub expected_client_propose_response: - Option>, - pub expected_client_read_response: Option>, - - #[allow(clippy::type_complexity)] - pub expected_metadata_response: Option< - Arc std::result::Result + Send + Sync>, - >, - - pub expected_snapshot_response: Option>, - pub expected_stream_snapshot_response: - Option>, - pub expected_purge_log_response: Option>, - pub expected_join_cluster_response: Option>, - pub expected_discover_leader_response: - Option>, -} - -#[tonic::async_trait] -impl RaftElectionService for MockRpcService { - async fn request_vote( - &self, - _request: tonic::Request, - ) -> std::result::Result, tonic::Status> { - match &self.expected_vote_response { - Some(Ok(response)) => Ok(tonic::Response::new(*response)), - Some(Err(status)) => Err(status.clone()), - None => Err(tonic::Status::unknown("No mock vote response set")), - } - } -} - -#[tonic::async_trait] -impl RaftReplicationService for MockRpcService { - async fn append_entries( - &self, - _request: tonic::Request, - ) -> std::result::Result, tonic::Status> { - match &self.expected_append_entries_response { - Some(Ok(response)) => Ok(tonic::Response::new(*response)), - Some(Err(status)) => Err(status.clone()), - None => Err(tonic::Status::unknown( - "No mock append entries response set", - )), - } - } -} - -#[tonic::async_trait] -impl ClusterManagementService for MockRpcService { - async fn update_cluster_conf( - &self, - _request: tonic::Request, - ) -> std::result::Result, tonic::Status> { - match &self.expected_update_cluster_conf_response { - Some(Ok(response)) => Ok(tonic::Response::new(*response)), - Some(Err(status)) => Err(status.clone()), - None => Err(tonic::Status::unknown( - "No mock update_cluster_conf response set", - )), - } - } - async fn get_cluster_metadata( - &self, - _request: tonic::Request, - ) -> std::result::Result, tonic::Status> { - match (&self.expected_metadata_response, self.server_port) { - (Some(f), Some(port)) => f(port).map(tonic::Response::new).map_err(|e| e.clone()), - _ => Err(tonic::Status::unimplemented( - "Metadata response not configured", - )), - } - } - - async fn join_cluster( - &self, - _request: tonic::Request, - ) -> std::result::Result, tonic::Status> { - match &self.expected_join_cluster_response { - Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), - Some(Err(status)) => Err(status.clone()), - None => Err(tonic::Status::unknown( - "No mock get_cluster_metadata response set", - )), - } - } - - async fn discover_leader( - &self, - _request: tonic::Request, - ) -> std::result::Result, tonic::Status> { - match &self.expected_discover_leader_response { - Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), - Some(Err(status)) => Err(status.clone()), - None => Err(tonic::Status::unknown( - "No mock get_cluster_metadata response set", - )), - } - } -} - -#[tonic::async_trait] -impl RaftClientService for MockRpcService { - async fn handle_client_write( - &self, - _request: tonic::Request, - ) -> std::result::Result, tonic::Status> { - match &self.expected_client_propose_response { - Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), - Some(Err(status)) => Err(status.clone()), - None => Err(tonic::Status::unknown( - "No mock handle_client_write response set", - )), - } - } - - async fn handle_client_read( - &self, - _request: tonic::Request, - ) -> std::result::Result, tonic::Status> { - match &self.expected_client_read_response { - Some(Ok(response)) => Ok(tonic::Response::new(response.clone())), - Some(Err(status)) => Err(status.clone()), - None => Err(tonic::Status::unknown( - "No mock handle_client_read response set", - )), - } - } -} - -#[tonic::async_trait] -impl SnapshotService for MockRpcService { - type StreamSnapshotStream = tonic::Streaming; - - async fn stream_snapshot( - &self, - _request: tonic::Request>, - ) -> std::result::Result, tonic::Status> { - match &self.expected_stream_snapshot_response { - Some(Ok(response)) => { - let streaming: Self::StreamSnapshotStream = - crate_test_snapshot_stream(vec![response.clone()]); - Ok(tonic::Response::new(streaming)) - } - Some(Err(status)) => Err(status.clone()), - None => Err(tonic::Status::unknown( - "No mock install_snapshot response set", - )), - } - } - async fn install_snapshot( - &self, - request: tonic::Request>, - ) -> std::result::Result, tonic::Status> { - let mut stream = request.into_inner(); - - while let Some(_chunk) = stream.next().await { - trace!("install_snapshot receive chunk - "); - } - trace!("install_snapshot no more to receive!"); - - match &self.expected_snapshot_response { - Some(Ok(response)) => return Ok(tonic::Response::new(*response)), - Some(Err(status)) => return Err(status.clone()), - None => { - return Err(tonic::Status::unknown( - "No mock install_snapshot response set", - )); - } - } - } - - async fn purge_log( - &self, - _request: tonic::Request, - ) -> std::result::Result, tonic::Status> { - match &self.expected_purge_log_response { - Some(Ok(response)) => Ok(tonic::Response::new(*response)), - Some(Err(status)) => Err(status.clone()), - None => Err(tonic::Status::unknown("No mock purge_log response set")), - } - } -} - -impl MockRpcService { - pub fn with_metadata_response( - mut self, - f: impl Fn(u16) -> std::result::Result + Send + Sync + 'static, - ) -> Self { - self.expected_metadata_response = Some(Arc::new(f)); - self - } - - pub fn set_port( - &mut self, - port: u16, - ) { - self.server_port = Some(port); - } -} - -pub struct MockNode; - -impl MockNode { - pub async fn mock_listener( - mut mock_service: MockRpcService, - rx: oneshot::Receiver<()>, - is_ready: bool, - ) -> Result<(u16, SocketAddr)> { - // Return port + address - let (mut health_reporter, health_service) = health_reporter(); - if is_ready { - health_reporter.set_serving::>().await; - health_reporter.set_serving::>().await; - health_reporter.set_serving::>().await; - health_reporter.set_serving::>().await; - health_reporter.set_serving::>().await; - info!("set service is serving"); - } else { - health_reporter.set_not_serving::>().await; - health_reporter.set_not_serving::>().await; - health_reporter - .set_not_serving::>() - .await; - health_reporter - .set_not_serving::>() - .await; - health_reporter.set_not_serving::>().await; - info!("set service is not serving"); - } - - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().map_err(StorageError::IoError)?; - let port = addr.port(); - debug!("starting mock rpc service:port={port}",); - - // Set the port in the service - mock_service.set_port(port); - - let mock_service = Arc::new(mock_service); - - let _r = tokio::spawn(async move { - tonic::transport::Server::builder() - .add_service(health_service) - .add_service( - RaftClientServiceServer::from_arc(mock_service.clone()) - .accept_compressed(CompressionEncoding::Gzip) - .send_compressed(CompressionEncoding::Gzip), - ) - .add_service( - RaftElectionServiceServer::from_arc(mock_service.clone()) - .accept_compressed(CompressionEncoding::Gzip) - .send_compressed(CompressionEncoding::Gzip), - ) - .add_service( - RaftReplicationServiceServer::from_arc(mock_service.clone()) - .accept_compressed(CompressionEncoding::Gzip) - .send_compressed(CompressionEncoding::Gzip), - ) - .add_service( - ClusterManagementServiceServer::from_arc(mock_service.clone()) - .accept_compressed(CompressionEncoding::Gzip) - .send_compressed(CompressionEncoding::Gzip), - ) - .add_service( - SnapshotServiceServer::from_arc(mock_service) - .accept_compressed(CompressionEncoding::Gzip) - .send_compressed(CompressionEncoding::Gzip), - ) - // add as a dev-dependency the crate `tokio-stream` with feature `net` enabled - .serve_with_incoming_shutdown( - tokio_stream::wrappers::TcpListenerStream::new(listener), - async { - rx.await.ok(); - }, - ) - // .serve_with_shutdown("127.0.0.1:50051".parse().unwrap(), ) - .await - .unwrap(); - }); - - Ok((port, addr)) // Return both port and address - } - - // Update helper functions to handle dynamic ports - pub(crate) async fn mock_channel_with_port(port: u16) -> Channel { - Channel::from_shared(format!("http://127.0.0.1:{port}")) - .expect("valid address") - .connect() - .await - .expect("connection failed") - } - - pub(crate) fn tcp_addr_to_http_addr(addr: String) -> String { - format!("http://{addr}") - } - - pub(crate) async fn simulate_send_votes_mock_server( - response: VoteResponse, - rx: oneshot::Receiver<()>, - ) -> Result<(Channel, u16)> { - // Return channel + port - //prepare learner's channel address inside membership config - let mock_service = MockRpcService { - expected_vote_response: Some(Ok(response)), - ..Default::default() - }; - let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; - let channel = Self::mock_channel_with_port(port).await; - Ok((channel, port)) - } - - pub(crate) async fn simulate_purge_mock_server( - response: PurgeLogResponse, - rx: oneshot::Receiver<()>, - ) -> Result<(Channel, u16)> { - // Return channel + port - //prepare learner's channel address inside membership config - let mock_service = MockRpcService { - expected_purge_log_response: Some(Ok(response)), - ..Default::default() - }; - let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; - let channel = Self::mock_channel_with_port(port).await; - Ok((channel, port)) - } - - // Return channel + port - pub(crate) async fn simulate_snapshot_mock_server( - response: std::result::Result, - rx: oneshot::Receiver<()>, - ) -> Result<(Channel, u16)> { - //prepare learner's channel address inside membership config - let mock_service = MockRpcService { - expected_snapshot_response: Some(response), - ..Default::default() - }; - let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; - let channel = Self::mock_channel_with_port(port).await; - Ok((channel, port)) - } - - #[allow(clippy::type_complexity)] - pub(crate) async fn simulate_mock_service_with_cluster_conf_reps( - rx: oneshot::Receiver<()>, - response_builder: Option< - Box std::result::Result + Send + Sync>, - >, - ) -> Result<(Channel, u16)> { - let builder = response_builder.unwrap_or_else(|| { - Box::new(|port: u16| { - Ok(ClusterMembership { - version: 1, - nodes: vec![NodeMeta { - id: 1, - role: Leader.into(), - address: format!("127.0.0.1:{port}",), - status: NodeStatus::Active.into(), - }], - }) - }) - }); - - let mock_service = MockRpcService::default().with_metadata_response(builder); - - let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; - let channel = Self::mock_channel_with_port(port).await; - Ok((channel, port)) - } - - pub(crate) async fn simulate_append_entries_mock_server( - response: std::result::Result, - rx: oneshot::Receiver<()>, - ) -> Result<(Channel, u16)> { - //prepare learner's channel address inside membership config - let mock_service = MockRpcService { - expected_append_entries_response: Some(response), - ..Default::default() - }; - let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; - let channel = Self::mock_channel_with_port(port).await; - Ok((channel, port)) - } - - #[allow(clippy::type_complexity)] - pub(crate) async fn simulate_mock_service_with_join_cluster_reps( - rx: oneshot::Receiver<()>, - metadata_response_builder: Option< - Box std::result::Result + Send + Sync>, - >, - join_response: std::result::Result, - ) -> Result<(Channel, u16)> { - let builder = metadata_response_builder.unwrap_or_else(|| { - Box::new(|port: u16| { - Ok(ClusterMembership { - version: 1, - nodes: vec![NodeMeta { - id: 1, - role: Leader.into(), - address: format!("127.0.0.1:{port}",), - status: NodeStatus::Active.into(), - }], - }) - }) - }); - let mock_service = MockRpcService { - expected_metadata_response: Some(Arc::new(builder)), - expected_join_cluster_response: Some(join_response), - ..Default::default() - }; - let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; - let channel = Self::mock_channel_with_port(port).await; - Ok((channel, port)) - } - - #[allow(clippy::type_complexity)] - pub(crate) async fn simulate_client_read_mock_server( - rx: oneshot::Receiver<()>, - metadata_response_builder: Option< - Box std::result::Result + Send + Sync>, - >, - response: ClientResponse, - ) -> Result<(Channel, u16)> { - let builder = metadata_response_builder.unwrap_or_else(|| { - Box::new(|port: u16| { - Ok(ClusterMembership { - version: 1, - nodes: vec![NodeMeta { - id: 1, - role: Leader.into(), - address: format!("127.0.0.1:{port}",), - status: NodeStatus::Active.into(), - }], - }) - }) - }); - let mock_service = MockRpcService { - expected_metadata_response: Some(Arc::new(builder)), - expected_client_read_response: Some(Ok(response)), - ..Default::default() - }; - let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; - let channel = Self::mock_channel_with_port(port).await; - Ok((channel, port)) - } - - #[allow(clippy::type_complexity)] - pub(crate) async fn simulate_client_write_mock_server( - rx: oneshot::Receiver<()>, - metadata_response_builder: Option< - Box std::result::Result + Send + Sync>, - >, - response: ClientResponse, - ) -> Result<(Channel, u16)> { - let builder = metadata_response_builder.unwrap_or_else(|| { - Box::new(|port: u16| { - Ok(ClusterMembership { - version: 1, - nodes: vec![NodeMeta { - id: 1, - role: Leader.into(), - address: format!("127.0.0.1:{port}",), - status: NodeStatus::Active.into(), - }], - }) - }) - }); - let mock_service = MockRpcService { - expected_metadata_response: Some(Arc::new(builder)), - expected_client_propose_response: Some(Ok(response)), - ..Default::default() - }; - let (port, _addr) = Self::mock_listener(mock_service, rx, true).await?; - let channel = Self::mock_channel_with_port(port).await; - Ok((channel, port)) - } -} diff --git a/d-engine-server/tests/components/raft_role/candidate_state_test.rs b/d-engine-server/tests/components/raft_role/candidate_state_test.rs index c3773a91..681668d7 100644 --- a/d-engine-server/tests/components/raft_role/candidate_state_test.rs +++ b/d-engine-server/tests/components/raft_role/candidate_state_test.rs @@ -21,8 +21,8 @@ use d_engine_core::RaftOneshot; use d_engine_core::RoleEvent; use d_engine_core::candidate_state::CandidateState; use d_engine_core::role_state::RaftRoleState; -use d_engine_core::test_utils::crate_test_snapshot_stream; use d_engine_core::test_utils::create_test_chunk; +use d_engine_core::test_utils::create_test_snapshot_stream; use d_engine_core::test_utils::mock_election_core; use d_engine_core::test_utils::mock_raft_context; use d_engine_proto::client::ClientReadRequest; @@ -618,7 +618,7 @@ async fn test_handle_raft_event_case7() { // Step 2: Prepare the InstallSnapshotChunk event let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); - let stream = crate_test_snapshot_stream(vec![create_test_chunk(0, b"chunk0", 1, 1, 2)]); + let stream = create_test_snapshot_stream(vec![create_test_chunk(0, b"chunk0", 1, 1, 2)]); let raft_event = RaftEvent::InstallSnapshotChunk(Box::new(stream), resp_tx); // Step 3: Call handle_raft_event diff --git a/d-engine-server/tests/components/raft_role/leader_state_test.rs b/d-engine-server/tests/components/raft_role/leader_state_test.rs index f43782b1..05fb7c8c 100644 --- a/d-engine-server/tests/components/raft_role/leader_state_test.rs +++ b/d-engine-server/tests/components/raft_role/leader_state_test.rs @@ -45,8 +45,8 @@ use d_engine_core::convert::safe_kv_bytes; use d_engine_core::leader_state::LeaderState; use d_engine_core::role_state::RaftRoleState; use d_engine_core::test_utils::MockBuilder; -use d_engine_core::test_utils::crate_test_snapshot_stream; use d_engine_core::test_utils::create_test_chunk; +use d_engine_core::test_utils::create_test_snapshot_stream; use d_engine_core::test_utils::mock_raft_context; use d_engine_core::test_utils::node_config; use d_engine_proto::client::ClientReadRequest; @@ -1008,7 +1008,7 @@ async fn test_handle_raft_event_case7() { let mut state = LeaderState::::new(1, context.node_config()); let (resp_tx, mut resp_rx) = MaybeCloneOneshot::new(); - let stream = crate_test_snapshot_stream(vec![create_test_chunk(0, b"chunk0", 1, 1, 2)]); + let stream = create_test_snapshot_stream(vec![create_test_chunk(0, b"chunk0", 1, 1, 2)]); let raft_event = RaftEvent::InstallSnapshotChunk(Box::new(stream), resp_tx); let (role_tx, mut role_rx) = mpsc::unbounded_channel(); diff --git a/d-engine-server/tests/components/raft_test.rs b/d-engine-server/tests/components/raft_test.rs index aeca34f9..5a661a46 100644 --- a/d-engine-server/tests/components/raft_test.rs +++ b/d-engine-server/tests/components/raft_test.rs @@ -54,7 +54,7 @@ async fn test_tick_priority_over_role_event() { raft.ctx.storage.raft_log = Arc::new(raft_log); raft.ctx.handlers.replication_handler = replication_core; // 2. Add state listeners - let role_tx = raft.role_tx.clone(); + let role_tx = raft.role_event_sender(); let (monitor_tx, mut monitor_rx) = mpsc::unbounded_channel::(); raft.register_role_transition_listener(monitor_tx); @@ -102,8 +102,8 @@ async fn test_role_event_priority_over_event_rx() { raft.ctx.handlers.replication_handler = replication_core; // 2. Add state listeners - let raft_tx = raft.event_tx.clone(); - let role_tx = raft.role_tx.clone(); + let raft_tx = raft.event_sender(); + let role_tx = raft.role_event_sender(); let (monitor_tx, mut monitor_rx) = mpsc::unbounded_channel::(); raft.register_role_transition_listener(monitor_tx.clone()); let (event_monitor_tx, mut event_monitor_rx) = mpsc::unbounded_channel::(); @@ -1000,7 +1000,7 @@ async fn test_handle_role_event_state_update_case1_5_1() { raft.ctx.handlers.replication_handler = replication_handler; // 2. Add state listeners - let role_tx = raft.role_tx.clone(); + let role_tx = raft.role_event_sender(); let (monitor_tx, mut monitor_rx) = mpsc::unbounded_channel::(); raft.register_role_transition_listener(monitor_tx); @@ -1058,7 +1058,7 @@ async fn test_handle_role_event_state_update_case1_5_2() { raft.ctx.handlers.replication_handler = replication_core; // 2. Add state listeners - let role_tx = raft.role_tx.clone(); + let role_tx = raft.role_event_sender(); let (monitor_tx, mut monitor_rx) = mpsc::unbounded_channel::(); raft.register_role_transition_listener(monitor_tx);