turmoil/lib.rs
1//! Turmoil is a framework for testing distributed systems. It provides
2//! deterministic execution by running multiple concurrent hosts within a single
3//! thread. It introduces "hardship" into the system via changes in the
4//! simulated network. The network can be controlled manually or with a seeded
5//! rng.
6//!
7//! # Hosts and Software
8//!
9//! A turmoil simulation is comprised of one or more hosts. Hosts run software,
10//! which is represented as a `Future`.
11//!
12//! Test code is also executed on a special host, called a client. This allows
13//! for an entry point into the simulated system. Client hosts have all the same
14//! capabilities as normal hosts, such as networking support.
15//!
16//! ```
17//! use turmoil;
18//!
19//! let mut sim = turmoil::Builder::new().build();
20//!
21//! // register a host
22//! sim.host("host", || async {
23//!
24//! // host software goes here
25//!
26//! Ok(())
27//! });
28//!
29//! // define test code
30//! sim.client("test", async {
31//!
32//! // we can interact with other hosts from here
33//!
34//! Ok(())
35//! });
36//!
37//! // run the simulation and handle the result
38//! _ = sim.run();
39//!
40//! ```
41//!
42//! # Networking
43//!
44//! Simulated networking types that mirror `tokio::net` are included in the
45//! `turmoil::net` module.
46//!
47//! Turmoil is not yet oppinionated on how to structure your application code to
48//! swap in simulated types under test. More on this coming soon...
49//!
50//! # Network Manipulation
51//!
52//! The simulation has the following network manipulation capabilities:
53//!
54//! * [`partition`], which introduces a network partition between hosts
55//! * [`repair`], which repairs a network partition between hosts
56//! * [`hold`], which holds all "in flight" messages between hosts. Messages are
57//! available for introspection using [`Sim`]'s `links` method.
58//! * [`release`], which releases all "in flight" messages between hosts
59//!
60//! # Tracing
61//!
62//! The `tracing` crate is used to emit important events during the lifetime of
63//! a simulation. To enable traces, your tests must install a
64//! [`tracing-subscriber`](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/).
65//! The log level of turmoil can be configured using `RUST_LOG=turmoil=info`.
66//!
67//! It is possible to configure your tracing subscriber to log elapsed
68//! simulation time instead of real time. See the grpc example.
69//!
70//! Turmoil can provide a full packet level trace of the events happening in a
71//! simulation by passing `RUST_LOG=turmoil=trace`. This is really useful
72//! when you are unable to identify why some unexpected behaviour is happening
73//! and you need to know which packets are reaching where.
74//!
75//! To see this in effect, you can run the axum example with the following
76//! command:
77//!
78//! ```bash
79//! RUST_LOG=INFO,turmoil=TRACE cargo run -p axum-example
80//! ```
81//!
82//! You can see the TCP packets being sent and delivered between the server
83//! and the client:
84//!
85//! ```bash
86//! ...
87//! 2023-11-29T20:23:43.276745Z TRACE node{name="server"}: turmoil: Send src=192.168.0.1:9999 dst=192.168.0.2:49152 protocol=TCP [0x48, 0x54, 0x54, 0x50, 0x2F, 0x31, 0x2E, 0x31, 0x20, 0x32, 0x30, 0x30, 0x20, 0x4F, 0x4B, 0xD, 0xA, 0x63, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x74, 0x79, 0x70, 0x65, 0x3A, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2F, 0x70, 0x6C, 0x61, 0x69, 0x6E, 0x3B, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3D, 0x75, 0x74, 0x66, 0x2D, 0x38, 0xD, 0xA, 0x63, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x6C, 0x65, 0x6E, 0x67, 0x74, 0x68, 0x3A, 0x20, 0x31, 0x30, 0xD, 0xA, 0x64, 0x61, 0x74, 0x65, 0x3A, 0x20, 0x57, 0x65, 0x64, 0x2C, 0x20, 0x32, 0x39, 0x20, 0x4E, 0x6F, 0x76, 0x20, 0x32, 0x30, 0x32, 0x33, 0x20, 0x32, 0x30, 0x3A, 0x32, 0x33, 0x3A, 0x34, 0x33, 0x20, 0x47, 0x4D, 0x54, 0xD, 0xA, 0xD, 0xA, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x66, 0x6F, 0x6F, 0x21]
88//! 2023-11-29T20:23:43.276834Z DEBUG turmoil::sim: step 43
89//! 2023-11-29T20:23:43.276907Z DEBUG turmoil::sim: step 44
90//! 2023-11-29T20:23:43.276981Z DEBUG turmoil::sim: step 45
91//! 2023-11-29T20:23:43.277039Z TRACE node{name="client"}: turmoil: Delivered src=192.168.0.1:9999 dst=192.168.0.2:49152 protocol=TCP [0x48, 0x54, 0x54, 0x50, 0x2F, 0x31, 0x2E, 0x31, 0x20, 0x32, 0x30, 0x30, 0x20, 0x4F, 0x4B, 0xD, 0xA, 0x63, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x74, 0x79, 0x70, 0x65, 0x3A, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2F, 0x70, 0x6C, 0x61, 0x69, 0x6E, 0x3B, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3D, 0x75, 0x74, 0x66, 0x2D, 0x38, 0xD, 0xA, 0x63, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x6C, 0x65, 0x6E, 0x67, 0x74, 0x68, 0x3A, 0x20, 0x31, 0x30, 0xD, 0xA, 0x64, 0x61, 0x74, 0x65, 0x3A, 0x20, 0x57, 0x65, 0x64, 0x2C, 0x20, 0x32, 0x39, 0x20, 0x4E, 0x6F, 0x76, 0x20, 0x32, 0x30, 0x32, 0x33, 0x20, 0x32, 0x30, 0x3A, 0x32, 0x33, 0x3A, 0x34, 0x33, 0x20, 0x47, 0x4D, 0x54, 0xD, 0xA, 0xD, 0xA, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x66, 0x6F, 0x6F, 0x21]
92//! 2023-11-29T20:23:43.277097Z TRACE node{name="client"}: turmoil: Recv src=192.168.0.1:9999 dst=192.168.0.2:49152 protocol=TCP [0x48, 0x54, 0x54, 0x50, 0x2F, 0x31, 0x2E, 0x31, 0x20, 0x32, 0x30, 0x30, 0x20, 0x4F, 0x4B, 0xD, 0xA, 0x63, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x74, 0x79, 0x70, 0x65, 0x3A, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2F, 0x70, 0x6C, 0x61, 0x69, 0x6E, 0x3B, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3D, 0x75, 0x74, 0x66, 0x2D, 0x38, 0xD, 0xA, 0x63, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x6C, 0x65, 0x6E, 0x67, 0x74, 0x68, 0x3A, 0x20, 0x31, 0x30, 0xD, 0xA, 0x64, 0x61, 0x74, 0x65, 0x3A, 0x20, 0x57, 0x65, 0x64, 0x2C, 0x20, 0x32, 0x39, 0x20, 0x4E, 0x6F, 0x76, 0x20, 0x32, 0x30, 0x32, 0x33, 0x20, 0x32, 0x30, 0x3A, 0x32, 0x33, 0x3A, 0x34, 0x33, 0x20, 0x47, 0x4D, 0x54, 0xD, 0xA, 0xD, 0xA, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x66, 0x6F, 0x6F, 0x21]
93//! 2023-11-29T20:23:43.277324Z INFO client: axum_example: Got response: Response { status: 200, version: HTTP/1.1, headers: {"content-type": "text/plain; charset=utf-8", "content-length": "10", "date": "Wed, 29 Nov 2023 20:23:43 GMT"}, body: b"Hello foo!" }
94//! ...
95//! ```
96//!
97//! Here the server is sending a response, before it is delivered to, and
98//! received by the client. Note that there are three steps to each packet
99//! trace in turmoil. We see `Send` when a packet is sent from one address
100//! to another. The packet is then `Delivered` to its destination, and when
101//! the destination reads the packet it is `Recv`'d.
102//!
103//! # Feature flags
104//!
105//! * `regex`: Enables regex host resolution through `ToIpAddrs`
106//!
107//! ## tokio_unstable
108//!
109//! Turmoil uses [unhandled_panic] to forward host panics as test failures. See
110//! [unstable features] to opt in.
111//!
112//! [unhandled_panic]:
113//! https://docs.rs/tokio/latest/tokio/runtime/struct.Builder.html#method.unhandled_panic
114//! [unstable features]: https://docs.rs/tokio/latest/tokio/#unstable-features
115
116#[cfg(doctest)]
117mod readme;
118
119mod builder;
120
121use std::{net::IpAddr, time::Duration};
122
123pub use builder::Builder;
124
125mod config;
126use config::Config;
127
128mod dns;
129use dns::Dns;
130pub use dns::{ToIpAddr, ToIpAddrs, ToSocketAddrs};
131
132mod envelope;
133use envelope::Envelope;
134pub use envelope::{Datagram, Protocol, Segment};
135
136mod error;
137pub use error::Result;
138
139mod host;
140use host::Host;
141
142mod ip;
143pub use ip::IpVersion;
144
145pub mod net;
146
147mod rt;
148use rt::Rt;
149
150mod sim;
151pub use sim::Sim;
152
153mod top;
154use top::Topology;
155pub use top::{LinkIter, LinksIter, SentRef};
156
157mod world;
158use world::World;
159
160const TRACING_TARGET: &str = "turmoil";
161
162/// Utility method for performing a function on all hosts in `a` against all
163/// hosts in `b`.
164pub(crate) fn for_pairs(a: &Vec<IpAddr>, b: &Vec<IpAddr>, mut f: impl FnMut(IpAddr, IpAddr)) {
165 for first in a {
166 for second in b {
167 // skip for the same host
168 if first != second {
169 f(*first, *second)
170 }
171 }
172 }
173}
174
175/// Returns how long the currently executing host has been executing for in
176/// virtual time.
177///
178/// Must be called from within a Turmoil simulation.
179pub fn elapsed() -> Duration {
180 World::current(|world| world.current_host().timer.elapsed())
181}
182
183/// Returns how long the simulation has been executing for in virtual time.
184///
185/// Will return None if the duration is not available, typically because
186/// there is no currently executing host or world.
187pub fn sim_elapsed() -> Option<Duration> {
188 World::try_current(|world| {
189 world
190 .try_current_host()
191 .map(|host| host.timer.sim_elapsed())
192 .ok()
193 })
194 .ok()
195 .flatten()
196}
197
198/// Lookup an IP address by host name.
199///
200/// Must be called from within a Turmoil simulation.
201pub fn lookup(addr: impl ToIpAddr) -> IpAddr {
202 World::current(|world| world.lookup(addr))
203}
204
205/// Perform a reverse DNS lookup, returning the hostname if the entry exists.
206///
207/// Must be called from within a Turmoil simulation.
208pub fn reverse_lookup(addr: IpAddr) -> Option<String> {
209 World::current(|world| world.reverse_lookup(addr).map(|h| h.to_owned()))
210}
211
212/// Lookup an IP address by host name. Use regex to match a number of hosts.
213///
214/// Must be called from within a Turmoil simulation.
215pub fn lookup_many(addr: impl ToIpAddrs) -> Vec<IpAddr> {
216 World::current(|world| world.lookup_many(addr))
217}
218
219/// Hold messages between two hosts, or sets of hosts, until [`release`] is
220/// called.
221///
222/// Must be called from within a Turmoil simulation.
223pub fn hold(a: impl ToIpAddrs, b: impl ToIpAddrs) {
224 World::current(|world| world.hold_many(a, b))
225}
226
227/// The opposite of [`hold`]. All held messages are immediately delivered.
228///
229/// Must be called from within a Turmoil simulation.
230pub fn release(a: impl ToIpAddrs, b: impl ToIpAddrs) {
231 World::current(|world| world.release_many(a, b))
232}
233
234/// Partition two hosts, or sets of hosts, resulting in all messages sent
235/// between them to be dropped.
236///
237/// Must be called from within a Turmoil simulation.
238pub fn partition(a: impl ToIpAddrs, b: impl ToIpAddrs) {
239 World::current(|world| world.partition_many(a, b))
240}
241
242/// Partition two hosts, or sets of hosts, in one direction.
243///
244/// Must be called from within a Turmoil simulation.
245pub fn partition_oneway(from: impl ToIpAddrs, to: impl ToIpAddrs) {
246 World::current(|world| world.partition_oneway_many(from, to))
247}
248
249/// Repair the connection between two hosts, or sets of hosts, resulting in
250/// messages to be delivered.
251///
252/// Must be called from within a Turmoil simulation.
253pub fn repair(a: impl ToIpAddrs, b: impl ToIpAddrs) {
254 World::current(|world| world.repair_many(a, b))
255}
256
257/// Repair the connection between two hosts, or sets of hosts, in one direction.
258///
259/// Must be called from within a Turmoil simulation.
260pub fn repair_oneway(from: impl ToIpAddrs, to: impl ToIpAddrs) {
261 World::current(|world| world.repair_oneway_many(from, to))
262}
263
264/// Return the number of established tcp streams on the current host.
265pub fn established_tcp_stream_count() -> usize {
266 World::current(|world| world.est_tcp_streams())
267}
268
269/// Return the number of established tcp streams on the given host.
270pub fn established_tcp_stream_count_on(addr: impl ToIpAddr) -> usize {
271 World::current(|world| world.est_tcp_streams_on(addr))
272}