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}