Skip to content

feat #369: prepare d-engine node image and CLI for Jepsen integration - #370

Merged
JoshuaChi merged 6 commits into
mainfrom
feature/369-jepsen
May 10, 2026
Merged

JoshuaChi merged 6 commits into
mainfrom
feature/369-jepsen

Conversation

@JoshuaChi

@JoshuaChi JoshuaChi commented May 10, 2026 •

Copy link
Copy Markdown
Contributor

What Does This PR Do?

Adds cas subcommand to the standalone demo CLI and extends the node Docker image with tools required by Jepsen nemesis packages (partition, kill, pause). Removes the embedded Jepsen suite that has been superseded by the standalone d-engine-jepsen repo.

Type:


Why Is This Needed?

For features: Link to approved issue #369

Jepsen integration (d-engine-jepsen) requires:

  1. cas CLI subcommand — all Jepsen workloads (bank, set, append) depend on atomic compare-and-swap
  2. iproute2 in the node image — partition nemesis uses ip to inject network rules
  3. wget in the node image — Jepsen nemesis setup downloads tooling at runtime
  4. Removal of docker/jepsen/ — Jepsen tests have moved to the standalone d-engine-jepsen repo to avoid coupling test infrastructure with the engine

Checklist

Required:

  • make test passes
  • Added tests for new code (bank_transfer_invariant_embedded)
  • Commits squashed to 1-2 logical units

If changing APIs:

  • Updated relevant docs (docker/README.md)
  • Explained why complexity is justified

Testing

How tested:

  • Unit tests: bank_transfer_invariant_embedded covers CAS-based concurrent transfer invariant
  • Integration tests: Jepsen make test-all (register/bank/set/append) passes in d-engine-jepsen
  • Manual testing: client-usage-standalone-demo cas <key> <expected> <new> verified against live 3-node cluster

For bug fixes:

  • Added test that fails without this fix

Does This Follow d-engine's Principles?

  • Solves a real problem for most users (not just my edge case)
  • Keeps implementation simple
  • Doesn't bloat the API surface

Reviewer Notes

The docker/jepsen/ subtree deletion is intentional — all Jepsen infrastructure now lives in the d-engine-jepsen repo. The embedded vendor crate (dengine_ctl) and its Makefile/Dockerfile are no longer needed.

Estimated review complexity:

  • Quick (< 100 lines)
  • Medium (< 300 lines)
  • Deep (> 300 lines)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Cas CLI subcommand for atomic compare-and-swap operations in the example client.
    • Added integration test validating concurrent compare-and-swap transfers with node failure scenarios.
  • Documentation

    • Updated setup guide for Docker example environment and cluster configuration.
  • Chores

    • Optimized Docker build process with multi-stage build and cached compilation.
    • Updated container images to v0.2.4 and Jepsen testing image to v2.0.
    • Removed legacy configuration and testing infrastructure files.

JoshuaChi added 4 commits May 8, 2026 14:38
- Delete docker/jepsen/: moved to d-engine-jepsen repo
- Delete docker/config/n*.toml: config now mounted via ../config directly
- Fix Dockerfile: workspace-root build context, bookworm base, BuildKit cache mounts
- Fix docker-compose.yml: config volume default ./config -> ../config
- Update README: remove symlink step, reference d-engine-jepsen for tests
- iproute2: partition nemesis needs `ip` to enumerate network interfaces
- wget: nc/nemesis-packages setup! downloads bitflip binary via wget
@coderabbitai

coderabbitai Bot commented May 10, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@JoshuaChi has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 27 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f9631699-02fa-4a10-a7bd-6704b3e964c6

📥 Commits

Reviewing files that changed from the base of the PR and between a73c95b and 85adb4d.

📒 Files selected for processing (4)
  • .gitignore
  • d-engine-server/tests/cas_operations/bank_transfer_invariant_embedded.rs
  • examples/three-nodes-standalone/docker/Dockerfile
  • examples/three-nodes-standalone/docker/README.md
📝 Walkthrough

Walkthrough

This PR adds CAS (compare-and-swap) testing capabilities, refactors Docker infrastructure into a multi-stage build, updates configuration and documentation for Docker deployment, and removes deprecated Jepsen-based testing infrastructure. The changes update example applications and testing frameworks while simplifying deployment configurations.

Changes

Testing & Example Infrastructure Update

Layer / File(s) Summary
Test Infrastructure Foundation
.dockerignore
Excludes **/target/ and .git/ from Docker build context to reduce image size and build time.
CAS Testing
d-engine-server/tests/cas_operations/bank_transfer_invariant_embedded.rs, d-engine-server/tests/cas_operations/mod.rs
New RocksDB-gated integration test validates bank total invariant under concurrent CAS transfers with mid-flight follower failure; test module registration added to CAS test suite.
CLI CAS Support
examples/client-usage-standalone/src/main.rs
Adds Cas subcommand with key, expected, and new_value parameters; new handle_cas async handler invokes client's compare_and_swap API.
Docker Build Refactor
examples/three-nodes-standalone/docker/Dockerfile
Multi-stage build: dedicated rust:slim-bookworm builder stage with clang, libclang-dev, cmake; builds binary with cargo cache mounts; runtime stage copies prebuilt binary and installs only runtime dependencies.
Docker Deployment Config
examples/three-nodes-standalone/docker/docker-compose.yml
Updates image versions to demo:v0.2.4 and jepsen:2.0; adjusts config path default from ./config to ../config.
Documentation & Setup
examples/three-nodes-standalone/docker/README.md
Replaces generic Jepsen walkthrough with versioned, directory-specific setup guide; defines new workflow with output directory creation, updated build/startup/test commands, and WORKLOAD override example.
Deprecated Node Configs Removed
examples/three-nodes-standalone/docker/config/n1.toml, n2.toml, n3.toml, n4.toml, n5.toml
All node configuration files deleted (node IDs, cluster membership, Raft/persistence/network tuning parameters no longer present).
Jepsen Infrastructure Removed
examples/three-nodes-standalone/docker/jepsen/Dockerfile, Makefile, README.md, entrypoint.sh, project.clj, vendor/dengine_ctl/Cargo.toml, vendor/dengine_ctl/README.md, vendor/dengine_ctl/src/main.rs
Entire Jepsen testing suite removed: container build pipeline, test orchestration targets (make test/view/report/clean), SSH setup, Leiningen dependencies, and standalone DEngine CLI client.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • deventlab/d-engine#369: The additions of the CAS CLI subcommand, bank transfer invariant test, and .dockerignore directly address objectives described in this issue for expanding CAS operation testing and infrastructure updates.

Possibly related PRs

  • deventlab/d-engine#206: This PR's Docker/testing infrastructure changes and configuration removals are part of the broader v0.2.0 release refactoring work.

Poem

🐰 A test for swaps, with balances aligned,
Docker builds faster with stages designed,
Old Jepsen tools fade as new paths take flight,
The bank stays intact through the concurrent night. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main objective: preparing the d-engine node image and CLI for Jepsen integration by adding the cas subcommand, updating Docker configuration, and removing the embedded Jepsen suite.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/369-jepsen

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
examples/three-nodes-standalone/docker/README.md (1)

41-47: ⚡ Quick win

Directory switching may confuse users.

The workflow requires switching between d-engine (steps 1-3) and d-engine-jepsen (steps 2.3 and 4) directories multiple times. Consider adding explicit cd commands or a clearer directory context indicator for each step.

🧭 Proposed improvement
 ### 4. Run Jepsen Tests
 
 ```bash
-# From d-engine-jepsen directory
+# Switch to the Jepsen test repository
 cd ../d-engine-jepsen/
+
+# Run all Jepsen workloads
 make test
 
 # Run a specific workload (register / bank / set)
 make test WORKLOAD=bank

Alternatively, add a navigation section at the beginning summarizing the directory structure:
```markdown
## Directory Structure
- `d-engine/` - Main repository (workspace root)
- `d-engine-jepsen/` - External Jepsen test suite (sibling directory)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/three-nodes-standalone/docker/README.md` around lines 41 - 47, The
README currently switches directories implicitly and may confuse users; update
the instructions to show explicit directory context by adding clear cd commands
and descriptive comments: prepend a navigation section listing the two
repositories (d-engine and d-engine-jepsen), update the code block to include an
explicit "Switch to the main repository" or "Switch to the Jepsen test
repository" comment and a cd ../d-engine-jepsen/ line before running make test,
and keep the examples for running "make test" and "make test WORKLOAD=bank"
(reference symbols: directories "d-engine" and "d-engine-jepsen" and commands
"make test" and "WORKLOAD=bank") so users know exactly which directory to be in
for each step.
d-engine-server/tests/cas_operations/bank_transfer_invariant_embedded.rs (1)

77-78: ⚡ Quick win

Avoid hardcoded /tmp paths for the per-node config file.

/tmp/d-engine-bank-node{node_id}.toml lives outside temp_dir, so it (a) is not cleaned up after the test, (b) is not portable to environments without /tmp (e.g. Windows CI), and (c) can collide with any other test that uses the same naming pattern (the #[serial] annotation only guards within a binary, not across binaries running in parallel). Writing the config under temp_dir keeps the test self-contained.

♻️ Proposed fix to keep the config inside the temp dir
         let (storage, state_machine) = RocksDBUnifiedEngine::open(&db_path)?;
-        let config_path = format!("/tmp/d-engine-bank-node{node_id}.toml");
-        tokio::fs::write(&config_path, &config_str).await?;
+        let config_path = temp_dir
+            .path()
+            .join(format!("d-engine-bank-node{node_id}.toml"));
+        tokio::fs::write(&config_path, &config_str).await?;
 
         let engine = EmbeddedEngine::start_custom(
             Arc::new(storage),
             Arc::new(state_machine),
-            Some(&config_path),
+            Some(config_path.to_str().unwrap()),
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@d-engine-server/tests/cas_operations/bank_transfer_invariant_embedded.rs`
around lines 77 - 78, The test currently writes the per-node config to a
hardcoded "/tmp/d-engine-bank-node{node_id}.toml"; change this to create the
config file inside the existing temp_dir instead (use temp_dir.path() combined
with format!("d-engine-bank-node{node_id}.toml") to build the file name), assign
that joined path to config_path (replacing the hardcoded string), and then call
tokio::fs::write on that path so the file is created within temp_dir, ensuring
cleanup, portability, and no cross-test collisions; update any uses of
config_path accordingly.
d-engine-server/tests/cas_operations/mod.rs (1)

6-13: 💤 Low value

Optional: list the new bank-invariant scenario in the module doc.

The module-level doc explicitly enumerates each scenario and each embedded test file (lines 6–13 and 37–40), so adding the new bank_transfer_invariant_embedded.rs there keeps the doc in sync and helps future readers find the scenario.

📝 Suggested doc additions
 //! 5. **Leader Failover + CAS (Embedded)**: CAS atomicity during leader crash
 //! 6. **Leader Failover + CAS (Standalone)**: gRPC client retry after leader crash
+//! 7. **Bank Transfer Invariant (Embedded)**: Concurrent CAS transfers preserve total balance under follower failure
 //! - ✅ **Embedded Mode**: In-process EmbeddedClient
 //!   - `distributed_lock_embedded.rs` - Multi-client lock competition
 //!   - `snapshot_recovery_embedded.rs` - CAS state persistence
 //!   - `leader_failover_cas_embedded.rs` - CAS atomicity during leader crash
+//!   - `bank_transfer_invariant_embedded.rs` - Concurrent transfers preserve total balance under follower failure

Also applies to: 37-40, 84-84

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@d-engine-server/tests/cas_operations/mod.rs` around lines 6 - 13, The module
doc lists the test scenarios but omits the new
bank_transfer_invariant_embedded.rs test; update the module-level documentation
comment to include a new entry for "Bank Transfer Invariant (Embedded)" and also
add "bank_transfer_invariant_embedded.rs" to the enumerated embedded test file
lists (the same doc sections currently listing other *_embedded.rs files) so the
scenario and filename appear alongside the existing entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@d-engine-server/tests/cas_operations/bank_transfer_invariant_embedded.rs`:
- Around line 27-30: The file fails rustfmt: reformat the unpack function and
the test function signature to match cargo fmt; change the unpack function
(unpack) so the returned [u64; 3] array is written across multiple lines with
each element on its own line (e.g., open bracket, each element expression on its
own line, close bracket) and adjust the test function signature to a single-line
form (e.g., fn test_name() { ...) so cargo fmt --all -- --check passes; run
cargo fmt to verify.

In `@examples/three-nodes-standalone/docker/Dockerfile`:
- Line 86: The Dockerfile currently starts three services by backgrounding them
in the CMD string (sshd, demo, promtail), which prevents proper signal
propagation and process supervision; instead, add a lightweight init/supervisor
(e.g., tini or supervisord), create an entrypoint script (e.g.,
/app/entrypoint.sh) that starts sshd and promtail in the background and then
uses exec to run the foreground process (demo) so PID 1 is the main process,
update the Dockerfile to install/configure the chosen init (tini) and set
ENTRYPOINT to run it and CMD to invoke the entrypoint script; ensure the
entrypoint uses exec for demo so signals are forwarded and backgrounded services
are children of the init process for correct reaping and shutdown.

In `@examples/three-nodes-standalone/docker/README.md`:
- Around line 32-35: Clarify that the docker-compose commands must be run from
the examples/three-nodes-standalone/docker/ directory (because
docker-compose.yml uses CONFIG_PATH default ../config), update the README to
show changing into that directory (cd examples/three-nodes-standalone/docker/)
before running docker-compose up -d for the main cluster, and instruct using
docker-compose -f monitoring/docker-compose.yml up -d (or docker-compose up -d
from the monitoring subdir) for the monitoring stack so relative volume paths in
docker-compose.yml resolve correctly.

---

Nitpick comments:
In `@d-engine-server/tests/cas_operations/bank_transfer_invariant_embedded.rs`:
- Around line 77-78: The test currently writes the per-node config to a
hardcoded "/tmp/d-engine-bank-node{node_id}.toml"; change this to create the
config file inside the existing temp_dir instead (use temp_dir.path() combined
with format!("d-engine-bank-node{node_id}.toml") to build the file name), assign
that joined path to config_path (replacing the hardcoded string), and then call
tokio::fs::write on that path so the file is created within temp_dir, ensuring
cleanup, portability, and no cross-test collisions; update any uses of
config_path accordingly.

In `@d-engine-server/tests/cas_operations/mod.rs`:
- Around line 6-13: The module doc lists the test scenarios but omits the new
bank_transfer_invariant_embedded.rs test; update the module-level documentation
comment to include a new entry for "Bank Transfer Invariant (Embedded)" and also
add "bank_transfer_invariant_embedded.rs" to the enumerated embedded test file
lists (the same doc sections currently listing other *_embedded.rs files) so the
scenario and filename appear alongside the existing entries.

In `@examples/three-nodes-standalone/docker/README.md`:
- Around line 41-47: The README currently switches directories implicitly and
may confuse users; update the instructions to show explicit directory context by
adding clear cd commands and descriptive comments: prepend a navigation section
listing the two repositories (d-engine and d-engine-jepsen), update the code
block to include an explicit "Switch to the main repository" or "Switch to the
Jepsen test repository" comment and a cd ../d-engine-jepsen/ line before running
make test, and keep the examples for running "make test" and "make test
WORKLOAD=bank" (reference symbols: directories "d-engine" and "d-engine-jepsen"
and commands "make test" and "WORKLOAD=bank") so users know exactly which
directory to be in for each step.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f33d27f7-ef3e-4b00-b847-46d09b44c1f3

📥 Commits

Reviewing files that changed from the base of the PR and between c54309e and a73c95b.

📒 Files selected for processing (21)
  • .dockerignore
  • d-engine-server/tests/cas_operations/bank_transfer_invariant_embedded.rs
  • d-engine-server/tests/cas_operations/mod.rs
  • examples/client-usage-standalone/src/main.rs
  • examples/three-nodes-standalone/docker/Dockerfile
  • examples/three-nodes-standalone/docker/README.md
  • examples/three-nodes-standalone/docker/config/n1.toml
  • examples/three-nodes-standalone/docker/config/n2.toml
  • examples/three-nodes-standalone/docker/config/n3.toml
  • examples/three-nodes-standalone/docker/config/n4.toml
  • examples/three-nodes-standalone/docker/config/n5.toml
  • examples/three-nodes-standalone/docker/docker-compose.yml
  • examples/three-nodes-standalone/docker/jepsen/Dockerfile
  • examples/three-nodes-standalone/docker/jepsen/Makefile
  • examples/three-nodes-standalone/docker/jepsen/README.md
  • examples/three-nodes-standalone/docker/jepsen/entrypoint.sh
  • examples/three-nodes-standalone/docker/jepsen/project.clj
  • examples/three-nodes-standalone/docker/jepsen/src/jepsen/d_engine.clj
  • examples/three-nodes-standalone/docker/jepsen/vendor/dengine_ctl/Cargo.toml
  • examples/three-nodes-standalone/docker/jepsen/vendor/dengine_ctl/README.md
  • examples/three-nodes-standalone/docker/jepsen/vendor/dengine_ctl/src/main.rs
💤 Files with no reviewable changes (14)
  • examples/three-nodes-standalone/docker/jepsen/vendor/dengine_ctl/README.md
  • examples/three-nodes-standalone/docker/jepsen/project.clj
  • examples/three-nodes-standalone/docker/jepsen/Dockerfile
  • examples/three-nodes-standalone/docker/config/n3.toml
  • examples/three-nodes-standalone/docker/config/n5.toml
  • examples/three-nodes-standalone/docker/jepsen/src/jepsen/d_engine.clj
  • examples/three-nodes-standalone/docker/config/n1.toml
  • examples/three-nodes-standalone/docker/config/n4.toml
  • examples/three-nodes-standalone/docker/jepsen/README.md
  • examples/three-nodes-standalone/docker/jepsen/Makefile
  • examples/three-nodes-standalone/docker/config/n2.toml
  • examples/three-nodes-standalone/docker/jepsen/entrypoint.sh
  • examples/three-nodes-standalone/docker/jepsen/vendor/dengine_ctl/Cargo.toml
  • examples/three-nodes-standalone/docker/jepsen/vendor/dengine_ctl/src/main.rs

Comment thread examples/three-nodes-standalone/docker/Dockerfile
Comment thread examples/three-nodes-standalone/docker/README.md Outdated
@codecov

codecov Bot commented May 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…om git

- Exclude .rules and .github/copilot-instructions.md from version control
  (consistent with existing CLAUDE.md / .claude/ policy)
- Fix README ambiguity: docker-compose must run from docker/ so that
  relative volume paths (../config) resolve correctly
@JoshuaChi
JoshuaChi merged commit bc17dfe into main May 10, 2026
9 checks passed
@JoshuaChi
JoshuaChi deleted the feature/369-jepsen branch May 10, 2026 08:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant