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}