Skip to main content

isoloom_render/container/
host.rs

1//! The VM the containers run on (a cloud VM, a local VM): its size, every container's at once.
2
3use isoloom_plan::{Environment, MachineShape};
4
5/// The size of the VM the containers run on.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7#[non_exhaustive]
8pub struct HostSize {
9    /// CPUs: one per container unless it says, at least 2, at most 16.
10    pub cpus: u32,
11    /// Memory in MB: 512 per container unless it says, plus 1 GB for Docker itself.
12    pub memory_mb: u32,
13    /// Disk in GB: 5 per container unless it says, at least 20.
14    pub disk_gb: u32,
15}
16
17impl HostSize {
18    /// The host's size for the environment's containers.
19    #[must_use]
20    pub fn of(env: &Environment) -> HostSize {
21        let containers = env.machines.values().filter(|m| m.shape == MachineShape::Container);
22        let (cpus, memory_mb, disk_gb) = containers.fold((0_u32, 1024_u32, 0_u32), |(cpus, memory, disk), m| {
23            (
24                cpus.saturating_add(m.size.cpus.unwrap_or(1)),
25                memory.saturating_add(m.size.memory_mb.unwrap_or(512)),
26                disk.saturating_add(m.size.disk_gb.unwrap_or(5)),
27            )
28        });
29        HostSize {
30            cpus: cpus.clamp(2, 16),
31            memory_mb,
32            disk_gb: disk_gb.max(20),
33        }
34    }
35
36    /// One of three sizes of VM by its memory: up to 4 GB, up to 8 GB, more.
37    #[must_use]
38    pub fn pick<T: Copy>(&self, [small, medium, large]: [T; 3]) -> T {
39        match self.memory_mb {
40            0..=4096 => small,
41            4097..=8192 => medium,
42            _ => large,
43        }
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use isoloom_spec::model::Target;
50
51    use super::HostSize;
52    use crate::testing;
53
54    /// The containers' host for `yaml`, on `docker-vm`.
55    fn host_size(yaml: &str) -> HostSize {
56        HostSize::of(&testing::env(yaml, Target::DockerVm))
57    }
58
59    #[test]
60    fn every_container_counts_and_docker_takes_a_gigabyte() {
61        let size = host_size(
62            "version: 1\nname: t\nnetworks: { lan: { cidr: 10.9.0.0/24 } }\nmachines:\n  a: { networks: { lan: 10 }, app: { image: nginx } }\n  b: { networks: { lan: 11 }, resources: { cpus: 3, memory_mb: 4096, disk_gb: 30 }, app: { image: nginx } }\n",
63        );
64        assert_eq!(
65            size,
66            HostSize {
67                cpus: 4,
68                memory_mb: 512 + 4096 + 1024,
69                disk_gb: 35
70            }
71        );
72        assert_eq!(size.pick(["small", "medium", "large"]), "medium");
73    }
74
75    #[test]
76    fn a_few_small_containers_take_the_smallest_host() {
77        let size =
78            host_size("version: 1\nname: t\nnetworks: { lan: { cidr: 10.9.0.0/24 } }\nmachines:\n  a: { networks: { lan: 10 }, app: { image: nginx } }\n");
79        assert_eq!((size.cpus, size.memory_mb, size.disk_gb), (2, 1536, 20));
80        assert_eq!(size.pick(["small", "medium", "large"]), "small");
81    }
82
83    #[test]
84    fn many_containers_take_at_most_sixteen_cpus_and_the_largest_host() {
85        let machines: String = (0..20)
86            .map(|i| format!("  m{i}: {{ networks: {{ lan: {} }}, app: {{ image: nginx }} }}\n", 10 + i))
87            .collect::<Vec<_>>()
88            .concat();
89        let size = host_size(&format!(
90            "version: 1\nname: t\nnetworks: {{ lan: {{ cidr: 10.9.0.0/24 }} }}\nmachines:\n{machines}"
91        ));
92        assert_eq!((size.cpus, size.memory_mb, size.disk_gb), (16, 20 * 512 + 1024, 100));
93        assert_eq!(size.pick(["small", "medium", "large"]), "large");
94    }
95}