turmoil/builder.rs
1use std::{ops::RangeInclusive, time::SystemTime};
2
3use rand::{rngs::SmallRng, 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/// Turmoil uses a random number generator to determine the variation in random
54/// factors that affect the simulation, like message latency, and failure
55/// distributions. By default, this rng is randomly seeded. To make your tests
56/// deterministic, you can provide your own seed through [`Builder::rng_seed`].
57pub struct Builder {
58 config: Config,
59 ip_version: IpVersion,
60 link: config::Link,
61 rng_seed: Option<u64>,
62}
63
64impl Default for Builder {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70impl Builder {
71 pub fn new() -> Self {
72 Self {
73 config: Config::default(),
74 ip_version: IpVersion::default(),
75 link: config::Link {
76 latency: Some(config::Latency::default()),
77 message_loss: Some(config::MessageLoss::default()),
78 },
79 rng_seed: None,
80 }
81 }
82
83 /// When the simulation starts.
84 pub fn epoch(&mut self, value: SystemTime) -> &mut Self {
85 self.config.epoch = value;
86 self
87 }
88
89 /// How long the test should run for in simulated time
90 pub fn simulation_duration(&mut self, value: Duration) -> &mut Self {
91 self.config.duration = value;
92 self
93 }
94
95 /// How much simulated time should elapse each tick.
96 pub fn tick_duration(&mut self, value: Duration) -> &mut Self {
97 self.config.tick = value;
98 self
99 }
100
101 /// Which kind of network should be simulated.
102 pub fn ip_version(&mut self, value: IpVersion) -> &mut Self {
103 self.ip_version = value;
104 self
105 }
106
107 /// The minimum latency that a message will take to transfer over a
108 /// link on the network.
109 pub fn min_message_latency(&mut self, value: Duration) -> &mut Self {
110 self.link.latency_mut().min_message_latency = value;
111 self
112 }
113
114 /// The maximum latency that a message will take to transfer over a
115 /// link on the network.
116 pub fn max_message_latency(&mut self, value: Duration) -> &mut Self {
117 self.link.latency_mut().max_message_latency = value;
118 self
119 }
120
121 /// The failure rate of messages on the network. For TCP
122 /// this will break connections as currently there are
123 /// no re-send capabilities. With UDP this is useful for
124 /// testing network flakiness.
125 pub fn fail_rate(&mut self, value: f64) -> &mut Self {
126 self.link.message_loss_mut().fail_rate = value;
127 self
128 }
129
130 /// The repair rate of messages on the network. This is how
131 /// frequently a link is repaired after breaking.
132 pub fn repair_rate(&mut self, value: f64) -> &mut Self {
133 self.link.message_loss_mut().repair_rate = value;
134 self
135 }
136
137 /// The Dynamic Ports, also known as the Private or Ephemeral Ports.
138 /// See: <https://www.rfc-editor.org/rfc/rfc6335#section-6>
139 pub fn ephemeral_ports(&mut self, value: RangeInclusive<u16>) -> &mut Self {
140 self.config.ephemeral_ports = value;
141 self
142 }
143
144 /// Capacity of a host's TCP buffer in the sim.
145 pub fn tcp_capacity(&mut self, value: usize) -> &mut Self {
146 self.config.tcp_capacity = value;
147 self
148 }
149
150 /// Capacity of host's UDP buffer in the sim.
151 pub fn udp_capacity(&mut self, value: usize) -> &mut Self {
152 self.config.udp_capacity = value;
153 self
154 }
155
156 /// Enables the tokio I/O driver.
157 pub fn enable_tokio_io(&mut self) -> &mut Self {
158 self.config.enable_tokio_io = true;
159 self
160 }
161
162 /// Enables running of nodes in random order. This allows exploration
163 /// of extra state space in multi-node simulations where race conditions
164 /// may arise based on message send/receive order.
165 pub fn enable_random_order(&mut self) -> &mut Self {
166 self.config.random_node_order = true;
167 self
168 }
169
170 /// Seed for random numbers generated by turmoil.
171 pub fn rng_seed(&mut self, value: u64) -> &mut Self {
172 self.rng_seed = Some(value);
173 self
174 }
175
176 /// Build a simulation with the settings from the builder.
177 pub fn build<'a>(&self) -> Sim<'a> {
178 if self.link.latency().max_message_latency < self.link.latency().min_message_latency {
179 panic!("Maximum message latency must be greater than minimum.");
180 }
181
182 let rng = match self.rng_seed {
183 Some(seed) => SmallRng::seed_from_u64(seed),
184 None => SmallRng::from_entropy(),
185 };
186
187 let world = World::new(
188 self.link.clone(),
189 Box::new(rng),
190 self.ip_version.iter(),
191 self.config.tick,
192 );
193
194 Sim::new(self.config.clone(), world)
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use std::time::Duration;
201
202 use crate::Builder;
203
204 #[test]
205 #[should_panic]
206 fn invalid_latency() {
207 let _sim = Builder::new()
208 .min_message_latency(Duration::from_millis(100))
209 .max_message_latency(Duration::from_millis(50))
210 .build();
211 }
212}