Skip to main content

squib_core/
lifecycle.rs

1//! VM lifecycle phases — internal six-state enum.
2//!
3//! The internal phase enum is richer than the upstream three-value wire vocabulary so
4//! handlers can produce precise `fault_message`s on misordered requests. The wire form
5//! is a separate type owned by `squib-api`; this enum never crosses the HTTP boundary.
6//!
7//! See [11-runtime-core.md §
8//! 3.1](../../../specs/11-runtime-core.md#31-internal-lifecyclephase-vs-wire-vmstate).
9
10use core::fmt;
11
12/// The wire-shape vocabulary served by `GET /` — exactly the upstream three values.
13///
14/// Serializes to the literal upstream strings (`"Not started"`, `"Running"`, `"Paused"`)
15/// — note the space + lowercase `s` in `"Not started"`. SDKs and `firectl` sniff these
16/// strings; squib emits them verbatim.
17///
18/// `WireVmState` lives here (not in `squib-api`) so the `LifecyclePhase::wire_state`
19/// collapse function can be defined alongside its target without `squib-core` taking a
20/// dependency on the API crate.
21#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
22pub enum WireVmState {
23    /// VMM has started but no microvm is running. Wire string: `"Not started"`.
24    #[default]
25    NotStarted,
26    /// Microvm has booted and at least one vCPU is active. Wire string: `"Running"`.
27    Running,
28    /// Microvm has booted but vCPUs are paused. Wire string: `"Paused"`.
29    Paused,
30}
31
32impl fmt::Display for WireVmState {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        match self {
35            Self::NotStarted => f.write_str("Not started"),
36            Self::Running => f.write_str("Running"),
37            Self::Paused => f.write_str("Paused"),
38        }
39    }
40}
41
42/// Internal lifecycle phase — six values. Never serialized to the wire.
43#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
44pub enum LifecyclePhase {
45    /// No configuration posted yet.
46    #[default]
47    Uninitialized,
48    /// Configuration posted, awaiting `PUT /actions {InstanceStart}`.
49    NotStarted,
50    /// Boot orchestration in progress (vCPUs spawning, GIC live, devices wired).
51    Starting,
52    /// At least one vCPU is active.
53    Running,
54    /// Microvm booted but vCPUs are paused.
55    Paused,
56    /// Terminal state after PSCI `SYSTEM_OFF` / `SYSTEM_RESET` or fatal vCPU panic.
57    Shutdown,
58}
59
60impl LifecyclePhase {
61    /// Collapse to the upstream three-value vocabulary served by `GET /`.
62    ///
63    /// `Uninitialized | NotStarted | Starting | Shutdown` all map to
64    /// [`WireVmState::NotStarted`] — clients see only the upstream three values.
65    #[must_use]
66    pub const fn wire_state(self) -> WireVmState {
67        match self {
68            Self::Uninitialized | Self::NotStarted | Self::Starting | Self::Shutdown => {
69                WireVmState::NotStarted
70            }
71            Self::Running => WireVmState::Running,
72            Self::Paused => WireVmState::Paused,
73        }
74    }
75
76    /// `true` once the microvm has booted (Running or Paused).
77    #[must_use]
78    pub const fn is_post_boot(self) -> bool {
79        matches!(self, Self::Running | Self::Paused)
80    }
81
82    /// `true` while the VMM is still accepting pre-boot configuration mutations.
83    #[must_use]
84    pub const fn is_pre_boot(self) -> bool {
85        matches!(self, Self::Uninitialized | Self::NotStarted)
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn wire_state_collapses_per_spec_11_3_1() {
95        assert_eq!(
96            LifecyclePhase::Uninitialized.wire_state(),
97            WireVmState::NotStarted
98        );
99        assert_eq!(
100            LifecyclePhase::NotStarted.wire_state(),
101            WireVmState::NotStarted
102        );
103        assert_eq!(
104            LifecyclePhase::Starting.wire_state(),
105            WireVmState::NotStarted
106        );
107        assert_eq!(
108            LifecyclePhase::Shutdown.wire_state(),
109            WireVmState::NotStarted
110        );
111        assert_eq!(LifecyclePhase::Running.wire_state(), WireVmState::Running);
112        assert_eq!(LifecyclePhase::Paused.wire_state(), WireVmState::Paused);
113    }
114
115    #[test]
116    fn wire_state_displays_upstream_strings_verbatim() {
117        assert_eq!(WireVmState::NotStarted.to_string(), "Not started");
118        assert_eq!(WireVmState::Running.to_string(), "Running");
119        assert_eq!(WireVmState::Paused.to_string(), "Paused");
120    }
121
122    #[test]
123    fn pre_post_boot_predicates() {
124        assert!(LifecyclePhase::Uninitialized.is_pre_boot());
125        assert!(LifecyclePhase::NotStarted.is_pre_boot());
126        assert!(!LifecyclePhase::Starting.is_pre_boot());
127        assert!(LifecyclePhase::Running.is_post_boot());
128        assert!(LifecyclePhase::Paused.is_post_boot());
129        assert!(!LifecyclePhase::Shutdown.is_post_boot());
130    }
131}