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