Skip to main content

isoloom_spec/
sizing.rs

1//! What machines need when the spec doesn't say: the default size of one (sized for a VM, which
2//! needs more than a container), and what the whole spec adds up to.
3
4use crate::model::{self, Resources};
5
6/// A machine's CPUs when its `resources` don't say (defaults sized for a VM, which needs more
7/// than a container).
8pub const DEFAULT_CPUS: u32 = 1;
9/// A machine's memory when its `resources` don't say, in MB.
10pub const DEFAULT_MEMORY_MB: u32 = 1024;
11/// A machine's disk when its `resources` don't say, in GB.
12pub const DEFAULT_DISK_GB: u32 = 20;
13
14/// What the whole spec needs when every machine is a VM (the access machine included).
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub struct Totals {
17    /// How many machines.
18    pub machines: u32,
19    /// Their CPUs, added up.
20    pub cpus: u32,
21    /// Their memory in MB, added up.
22    pub memory_mb: u32,
23    /// Their disks in GB, added up.
24    pub disk_gb: u32,
25}
26
27/// What the whole spec needs when every machine is a VM, each machine's `resources` or the
28/// defaults: what `isoloom resources` reports before a run.
29#[must_use]
30pub fn totals(spec: &model::Spec) -> Totals {
31    spec.machines.values().fold(Totals::default(), |t, m| {
32        let r = m.resources.unwrap_or(Resources {
33            cpus: None,
34            memory_mb: None,
35            disk_gb: None,
36        });
37        // Saturating: a spec with absurd per-machine resources reports a capped total instead of
38        // overflowing (a debug panic / release wrap).
39        Totals {
40            machines: t.machines + 1,
41            cpus: t.cpus.saturating_add(r.cpus.unwrap_or(DEFAULT_CPUS)),
42            memory_mb: t.memory_mb.saturating_add(r.memory_mb.unwrap_or(DEFAULT_MEMORY_MB)),
43            disk_gb: t.disk_gb.saturating_add(r.disk_gb.unwrap_or(DEFAULT_DISK_GB)),
44        }
45    })
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn totals_add_each_machines_size_or_the_defaults() {
54        let spec = crate::testing::model(&crate::testing::lab(
55            "machines:\n  a: { networks: { lan: 10 }, docker: { image: x }, resources: { cpus: 4, memory_mb: 512, disk_gb: 5 } }\n  b: { networks: { lan: 11 }, docker: { image: x }, resources: { cpus: 2 } }\n  c: { networks: { lan: 12 }, docker: { image: x } }\n",
56        ))
57        .unwrap();
58        assert_eq!(
59            totals(&spec),
60            Totals {
61                machines: 3,
62                cpus: 4 + 2 + DEFAULT_CPUS,
63                memory_mb: 512 + 2 * DEFAULT_MEMORY_MB,
64                disk_gb: 5 + 2 * DEFAULT_DISK_GB,
65            }
66        );
67    }
68
69    #[test]
70    fn totals_saturate_instead_of_overflowing() {
71        let huge = u32::MAX;
72        let spec = crate::testing::model(&crate::testing::lab(&format!(
73            "machines:\n  a: {{ networks: {{ lan: 10 }}, docker: {{ image: x }}, resources: {{ cpus: {huge} }} }}\n  b: {{ networks: {{ lan: 11 }}, docker: {{ image: x }}, resources: {{ cpus: 3 }} }}\n"
74        )))
75        .unwrap();
76        assert_eq!(totals(&spec).cpus, u32::MAX);
77    }
78}