Skip to main content

turmoil/
builder.rs

1use std::{ops::RangeInclusive, time::SystemTime};
2
3use rand::{rngs::SmallRng, SeedableRng};
4
5#[cfg(feature = "unstable-fs")]
6use crate::fs::FsConfig;
7use crate::*;
8
9/// A builder that can be used to configure the simulation.
10///
11/// The Builder allows you to set a number of options when creating a turmoil
12/// simulation, see the available methods for documentation of each of these
13/// options.
14///
15/// ## Examples
16///
17/// You can use the builder to initialize a sim with default configuration:
18///
19/// ```
20/// let sim = turmoil::Builder::new().build();
21/// ```
22///
23/// If you want to vary factors of the simulation, you can use the
24/// respective Builder methods:
25///
26/// ```
27/// use std::time::{Duration, SystemTime};
28///
29/// let sim = turmoil::Builder::new()
30///     .simulation_duration(Duration::from_secs(60))
31///     .epoch(SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(946684800)).unwrap())
32///     .fail_rate(0.05) // 5% failure rate
33///     .build();
34/// ```
35///
36/// If you create a builder with a set of options you can then repeatedly
37/// call `build` to get a sim with the same settings:
38///
39/// ```
40/// use std::time::Duration;
41///
42/// // Create a persistent builder
43/// let mut builder = turmoil::Builder::new();
44///
45/// // Apply a chain of options to that builder
46/// builder.simulation_duration(Duration::from_secs(45))
47///     .fail_rate(0.05);
48///
49/// let sim_one = builder.build();
50/// let sim_two = builder.build();
51/// ````
52///
53/// ## Entropy and randomness
54///
55/// Turmoil uses a random number generator to determine the variation in random
56/// factors that affect the simulation, like message latency, and failure
57/// distributions. By default, this rng is randomly seeded. To make your tests
58/// deterministic, you can provide your own seed through [`Builder::rng_seed`].
59pub struct Builder {
60    config: Config,
61    ip_version: IpVersion,
62    link: config::Link,
63    rng_seed: Option<u64>,
64}
65
66impl Default for Builder {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72impl Builder {
73    pub fn new() -> Self {
74        Self {
75            config: Config::default(),
76            ip_version: IpVersion::default(),
77            link: config::Link {
78                latency: Some(config::Latency::default()),
79                message_loss: Some(config::MessageLoss::default()),
80            },
81            rng_seed: None,
82        }
83    }
84
85    /// When the simulation starts.
86    pub fn epoch(&mut self, value: SystemTime) -> &mut Self {
87        self.config.epoch = value;
88        self
89    }
90
91    /// How long the test should run for in simulated time
92    pub fn simulation_duration(&mut self, value: Duration) -> &mut Self {
93        self.config.duration = value;
94        self
95    }
96
97    /// How much simulated time should elapse each tick.
98    pub fn tick_duration(&mut self, value: Duration) -> &mut Self {
99        self.config.tick = value;
100        self
101    }
102
103    /// Which kind of network should be simulated.
104    pub fn ip_version(&mut self, value: IpVersion) -> &mut Self {
105        self.ip_version = value;
106        self
107    }
108
109    /// The minimum latency that a message will take to transfer over a
110    /// link on the network.
111    pub fn min_message_latency(&mut self, value: Duration) -> &mut Self {
112        self.link.latency_mut().min_message_latency = value;
113        self
114    }
115
116    /// The maximum latency that a message will take to transfer over a
117    /// link on the network.
118    pub fn max_message_latency(&mut self, value: Duration) -> &mut Self {
119        self.link.latency_mut().max_message_latency = value;
120        self
121    }
122
123    /// The failure rate of messages on the network. For TCP
124    /// this will break connections as currently there are
125    /// no re-send capabilities. With UDP this is useful for
126    /// testing network flakiness.
127    pub fn fail_rate(&mut self, value: f64) -> &mut Self {
128        self.link.message_loss_mut().fail_rate = value;
129        self
130    }
131
132    /// The repair rate of messages on the network. This is how
133    /// frequently a link is repaired after breaking.
134    pub fn repair_rate(&mut self, value: f64) -> &mut Self {
135        self.link.message_loss_mut().repair_rate = value;
136        self
137    }
138
139    /// The Dynamic Ports, also known as the Private or Ephemeral Ports.
140    /// See: <https://www.rfc-editor.org/rfc/rfc6335#section-6>
141    pub fn ephemeral_ports(&mut self, value: RangeInclusive<u16>) -> &mut Self {
142        self.config.ephemeral_ports = value;
143        self
144    }
145
146    /// Capacity of a host's TCP buffer in the sim.
147    pub fn tcp_capacity(&mut self, value: usize) -> &mut Self {
148        self.config.tcp_capacity = value;
149        self
150    }
151
152    /// Capacity of host's UDP buffer in the sim.
153    pub fn udp_capacity(&mut self, value: usize) -> &mut Self {
154        self.config.udp_capacity = value;
155        self
156    }
157
158    /// Enables the tokio I/O driver.
159    pub fn enable_tokio_io(&mut self) -> &mut Self {
160        self.config.enable_tokio_io = true;
161        self
162    }
163
164    /// Enables running of nodes in random order. This allows exploration
165    /// of extra state space in multi-node simulations where race conditions
166    /// may arise based on message send/receive order.
167    pub fn enable_random_order(&mut self) -> &mut Self {
168        self.config.random_node_order = true;
169        self
170    }
171
172    /// Access the filesystem configuration.
173    ///
174    /// Use builder-style methods to configure filesystem behavior:
175    ///
176    /// ```ignore
177    /// let sim = turmoil::Builder::new()
178    ///     .fs()
179    ///         .sync_probability(0.1)
180    ///         .capacity(1024 * 1024)
181    ///         .io_error_probability(0.01);
182    ///
183    /// // Continue with other builder methods
184    /// sim.build();
185    /// ```
186    #[cfg(feature = "unstable-fs")]
187    pub fn fs(&mut self) -> &mut FsConfig {
188        &mut self.config.fs
189    }
190
191    /// Seed for random numbers generated by turmoil.
192    pub fn rng_seed(&mut self, value: u64) -> &mut Self {
193        self.rng_seed = Some(value);
194        self
195    }
196
197    /// Build a simulation with the settings from the builder.
198    pub fn build<'a>(&self) -> Sim<'a> {
199        if self.link.latency().max_message_latency < self.link.latency().min_message_latency {
200            panic!("Maximum message latency must be greater than minimum.");
201        }
202
203        let rng = match self.rng_seed {
204            Some(seed) => SmallRng::seed_from_u64(seed),
205            None => SmallRng::from_os_rng(),
206        };
207
208        let world = World::new(
209            self.link.clone(),
210            Box::new(rng),
211            self.ip_version.iter(),
212            self.config.tick,
213        );
214
215        Sim::new(self.config.clone(), world)
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use std::time::Duration;
222
223    use crate::Builder;
224
225    #[test]
226    #[should_panic]
227    fn invalid_latency() {
228        let _sim = Builder::new()
229            .min_message_latency(Duration::from_millis(100))
230            .max_message_latency(Duration::from_millis(50))
231            .build();
232    }
233}