Skip to main content

RedisClusterTemplate

Struct RedisClusterTemplate 

Source
pub struct RedisClusterTemplate { /* private fields */ }
Expand description

Redis Cluster template for automatic multi-node cluster setup

Implementations§

Source§

impl RedisClusterTemplate

Source

pub fn new(name: impl Into<String>) -> Self

Create a new Redis Cluster template with default settings

Source

pub fn from_env(name: impl Into<String>) -> Self

Create a new Redis Cluster template with settings from environment variables.

Falls back to defaults if environment variables are not set.

§Environment Variables
  • REDIS_CLUSTER_PORT_BASE: Base port for Redis nodes (default: 7000)
  • REDIS_CLUSTER_NUM_MASTERS: Number of master nodes (default: 3)
  • REDIS_CLUSTER_NUM_REPLICAS: Number of replicas per master (default: 0)
  • REDIS_CLUSTER_PASSWORD: Password for cluster authentication (optional)
§Examples
use docker_wrapper::RedisClusterTemplate;

// Uses environment variables if set, otherwise uses defaults
let template = RedisClusterTemplate::from_env("my-cluster");
Source

pub fn get_port_base(&self) -> u16

Get the configured port base

Source

pub fn get_num_masters(&self) -> usize

Get the configured number of masters

Source

pub fn get_num_replicas(&self) -> usize

Get the configured number of replicas per master

Source

pub fn num_masters(self, masters: usize) -> Self

Set the number of master nodes (minimum 3)

Source

pub fn num_replicas(self, replicas: usize) -> Self

Set the number of replicas per master

Source

pub fn port_base(self, port: u16) -> Self

Set the base port for Redis nodes

Source

pub fn password(self, password: impl Into<String>) -> Self

Set cluster password

Source

pub fn cluster_announce_ip(self, ip: impl Into<String>) -> Self

Set the IP to announce to other cluster nodes

Source

pub fn with_persistence(self, volume_prefix: impl Into<String>) -> Self

Enable persistence with volume prefix

Source

pub fn memory_limit(self, limit: impl Into<String>) -> Self

Set memory limit per node

Source

pub fn cluster_node_timeout(self, timeout: u32) -> Self

Set cluster node timeout in milliseconds

Source

pub fn network_mode(self, mode: impl Into<String>) -> Self

Select the container network mode for cluster nodes.

Only "host" is special-cased: it is equivalent to calling host_network (see that method for the Linux-only caveats). Any other value leaves the cluster on its default, automatically managed bridge network, since a multi-node cluster relies on a private bridge for inter-node DNS and announce-IP wiring.

Source

pub fn host_network(self) -> Self

Run every cluster node with --network host.

In host networking mode each node shares the host’s network namespace, so its Redis port is a real host port and no published port mapping or --cluster-announce-ip ceremony is needed. To keep the nodes from colliding on a single shared namespace, each node listens on a distinct port derived from the port base (port_base + index), which is exactly the host port reported by node and RedisClusterConnection::from_template. The private bridge network is not created in this mode.

§Platform support

Host networking is a Linux-only Docker feature. On Docker Desktop for macOS and Windows the daemon runs inside a Linux VM, so --network host binds ports inside that VM rather than on your machine: the option is effectively a no-op there and the cluster will not be reachable from the host. This method does not return an error on non-Linux hosts (the Docker CLI accepts the flag regardless of backend); only use host mode against a native Linux daemon, such as a Linux CI runner.

§Example
use docker_wrapper::RedisClusterTemplate;

// Linux only: nodes are reachable on localhost:7000, 7001, 7002 with no
// bridge network and no announce-ip wiring.
let cluster = RedisClusterTemplate::new("host-cluster")
    .num_masters(3)
    .port_base(7000)
    .host_network();
Source

pub fn auto_remove(self) -> Self

Enable auto-remove when stopped

Source

pub fn with_redis_stack(self) -> Self

Use Redis Stack instead of standard Redis (includes modules like JSON, Search, Graph, TimeSeries, Bloom).

Uses the redis/redis-stack-server image pinned to a known-good default tag (7.4.0-v3) rather than latest, so that runs are reproducible. Call Self::stack_version to pin a different tag, or Self::custom_redis_image for full control.

Source

pub fn stack_version(self, tag: impl Into<String>) -> Self

Pin the Redis Stack server image tag (e.g. "7.4.0-v3").

Only affects the image used when Self::with_redis_stack is enabled. The default is a known-good pinned tag rather than latest, so that runs are reproducible. A Self::custom_redis_image takes precedence over this setting.

§Example
use docker_wrapper::RedisClusterTemplate;

let template = RedisClusterTemplate::new("my-cluster")
    .with_redis_stack()
    .stack_version("7.4.0-v3");
Source

pub fn with_redis_insight(self) -> Self

Enable RedisInsight GUI for cluster visualization and management.

Uses the redislabs/redisinsight image pinned to a known-good default tag (2.60) rather than latest, so that runs are reproducible. Call Self::redis_insight_version to pin a different tag.

Source

pub fn redis_insight_port(self, port: u16) -> Self

Set the port for RedisInsight UI (default: 8001)

Source

pub fn redis_insight_version(self, tag: impl Into<String>) -> Self

Pin the RedisInsight image tag (e.g. "2.60").

Only affects the image used when Self::with_redis_insight is enabled. The default is a known-good pinned tag rather than latest, so that runs are reproducible.

§Example
use docker_wrapper::RedisClusterTemplate;

let template = RedisClusterTemplate::new("my-cluster")
    .with_redis_insight()
    .redis_insight_version("2.60");
Source

pub fn custom_redis_image( self, image: impl Into<String>, tag: impl Into<String>, ) -> Self

Use a custom Redis image and tag

Source

pub fn platform(self, platform: impl Into<String>) -> Self

Set the platform for the containers (e.g., “linux/arm64”, “linux/amd64”)

Source

pub fn tls(self, certs_dir: impl Into<String>) -> Self

Enable TLS for every cluster node, bind-mounting the given host certificate directory read-only into each container.

The directory must contain these files (the same layout used by the single-node RedisTemplate):

  • redis.crt – the server certificate
  • redis.key – the server private key
  • ca.crt – the CA certificate used to verify peers

Each node is started with --tls-port set to its data port, --port 0 (plaintext disabled), and --tls-cluster yes/--tls-replication yes so that the cluster bus and replication links are encrypted too. This mirrors Redis’s documented cluster-over-TLS layout: unlike the single-node template, a TLS cluster is always TLS-only on the data port – the gossip protocol cannot mix plaintext and TLS nodes. The redis-cli --cluster create, readiness, and inspection calls all connect with --tls and the mounted certificates.

See RedisTemplate::tls for an openssl recipe to generate throwaway certificates for local testing.

§Example
use docker_wrapper::RedisClusterTemplate;

let cluster = RedisClusterTemplate::new("tls-cluster").tls("/path/to/certs");
Source

pub fn node_names(&self) -> Vec<String>

List the container names for every node in the cluster.

Names follow the deterministic {name}-node-{i} contract, ordered by node index from 0 to total_nodes() - 1. The first get_num_masters entries are masters and the remainder are replicas, mirroring how redis-cli --cluster create assigns roles.

This is the building block for targeted per-node fault injection: pick a name and pause, partition, or kill exactly that container.

§Examples
use docker_wrapper::RedisClusterTemplate;

let cluster = RedisClusterTemplate::new("chaos").num_masters(3);
assert_eq!(
    cluster.node_names(),
    vec!["chaos-node-0", "chaos-node-1", "chaos-node-2"],
);
Source

pub fn node(&self, index: usize) -> Option<ClusterNode>

Get a handle to a single node by index.

Returns a ClusterNode describing the node’s container name, the host port mapped to its Redis port, and its expected role, or None if index is out of range (index >= total_nodes()).

The returned values are derived from configuration only – this is a plain accessor that performs no Docker calls. The role is the role redis-cli --cluster create assigns: indices 0..num_masters are masters and the rest are replicas. To read the live role from a running node instead (for example after a failover), use node_role.

§Examples
use docker_wrapper::{NodeRole, RedisClusterTemplate};

let cluster = RedisClusterTemplate::new("chaos")
    .num_masters(3)
    .num_replicas(1)
    .port_base(7000);

let master = cluster.node(0).expect("node 0 exists");
assert_eq!(master.container_name, "chaos-node-0");
assert_eq!(master.host_port, 7000);
assert_eq!(master.role, NodeRole::Master);

// The first three nodes are masters, the last three are replicas.
let replica = cluster.node(3).expect("node 3 exists");
assert_eq!(replica.host_port, 7003);
assert_eq!(replica.role, NodeRole::Replica);

assert!(cluster.node(6).is_none());
Source

pub async fn node_role(&self, index: usize) -> Result<NodeRole, TemplateError>

Query the live role of a single node from the running container.

Runs redis-cli role inside the node’s container and reports whether the node currently identifies as a master or a replica. Unlike node, which returns the role assigned at creation time, this reflects the cluster’s current state (for example after a failover). This performs a Docker exec and is therefore not free; the cluster must be running.

§Errors

Returns an error if index is out of range, if the docker exec call fails (for example the container is not running), or if the role output cannot be parsed.

§Examples
let cluster = RedisClusterTemplate::new("my-cluster");
cluster.start().await?;

let role = cluster.node_role(0).await?;
println!("node 0 is currently a {:?}", role);
Source

pub async fn cluster_info(&self) -> Result<ClusterInfo, TemplateError>

Check cluster status

Source

pub async fn is_ready(&self) -> bool

Check if the cluster is ready (all nodes up, slots assigned).

Returns true if the cluster state is “ok”, false otherwise.

§Examples
let template = RedisClusterTemplate::new("my-cluster");
template.start().await?;

if template.is_ready().await {
    println!("Cluster is ready!");
}
Source

pub async fn wait_until_ready( &self, timeout: Duration, ) -> Result<(), TemplateError>

Wait for the cluster to become ready, with a timeout.

Polls the cluster state every 500ms until it reports “ok” or the timeout is exceeded.

§Errors

Returns an error if the timeout is exceeded before the cluster becomes ready.

§Examples
let template = RedisClusterTemplate::new("my-cluster");
template.start().await?;

// Wait up to 30 seconds for the cluster to be ready
template.wait_until_ready(Duration::from_secs(30)).await?;
println!("Cluster is ready!");
Source

pub async fn detect_existing(&self) -> Option<RedisClusterConnection>

Check if a Redis cluster is already running at the configured ports.

This is useful in CI environments where an external cluster may be provided (e.g., via grokzen/redis-cluster Docker image).

Returns connection info if a cluster is detected, None otherwise.

§Examples
let template = RedisClusterTemplate::from_env("my-cluster");

if let Some(conn) = template.detect_existing().await {
    println!("Found existing cluster: {}", conn.nodes_string());
} else {
    println!("No existing cluster found");
}
Source

pub async fn start_or_detect( &self, timeout: Duration, ) -> Result<RedisClusterConnection, TemplateError>

Start the cluster, or use an existing one if already running.

This provides a “best of both worlds” approach for hybrid local/CI setups:

  • In CI: Uses the externally-provided cluster without starting new containers
  • Locally: Starts a new cluster via docker-wrapper
§Examples
// Works in both CI (uses existing) and local (starts new)
let template = RedisClusterTemplate::from_env("test-cluster");
let conn = template.start_or_detect(Duration::from_secs(60)).await?;

println!("Cluster ready at: {}", conn.nodes_string());

Trait Implementations§

Source§

impl Template for RedisClusterTemplate

Source§

fn name(&self) -> &str

Get the template name
Source§

fn config(&self) -> &TemplateConfig

Get the template configuration
Source§

fn config_mut(&mut self) -> &mut TemplateConfig

Get a mutable reference to the configuration
Source§

fn start<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<String, TemplateError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Start the container with this template
Source§

fn stop<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<(), TemplateError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Stop the container
Source§

fn remove<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<(), TemplateError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Remove the container
Source§

fn build_command(&self) -> RunCommand

Build the RunCommand for this template
Source§

fn start_and_wait<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Start the container and wait for it to be ready
Source§

fn is_running<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<bool>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Check if the container is running
Source§

fn logs<'life0, 'life1, 'async_trait>( &'life0 self, follow: bool, tail: Option<&'life1 str>, ) -> Pin<Box<dyn Future<Output = Result<CommandOutput>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Get container logs
Source§

fn exec<'life0, 'life1, 'async_trait>( &'life0 self, command: Vec<&'life1 str>, ) -> Pin<Box<dyn Future<Output = Result<ExecOutput>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Execute a command in the running container
Source§

fn wait_for_ready<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Wait for the container to be ready Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more