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}