1pub mod actions;
15pub mod balloon;
16pub mod boot_source;
17pub mod common;
18pub mod config_file;
19pub mod cpu_config;
20pub mod drive;
21pub mod entropy;
22pub mod hotplug_memory;
23pub mod logger;
24pub mod machine_config;
25pub mod metrics;
26pub mod mmds;
27pub mod network;
28pub mod pmem;
29pub mod serial;
30pub mod snapshot;
31pub mod vm;
32pub mod vsock;
33
34pub use actions::{InstanceAction, InstanceActionInfo};
35pub use balloon::{BalloonConfig, BalloonHintingOp, BalloonStatsUpdate, BalloonUpdate};
36pub use boot_source::BootSourceConfig;
37pub use common::{
38 DriveId, IfaceId, InstanceId, MAX_DRIVES, MAX_NICS, MAX_PMEM, MAX_VIRTIO_MEM, MacAddr,
39 MemSizeMib, SafePath, UdsPath, VsockId,
40};
41pub use config_file::ConfigFile;
42pub use cpu_config::CpuConfig;
43pub use drive::{DriveConfig, DrivePatch};
44pub use entropy::EntropyConfig;
45pub use hotplug_memory::{HotplugMemoryConfig, HotplugMemoryUpdate};
46pub use logger::LoggerConfig;
47pub use machine_config::{MachineConfig, MachineConfigPatch};
48pub use metrics::MetricsConfig;
49pub use mmds::{MmdsConfig, MmdsContents};
50pub use network::{NetworkInterfaceConfig, NetworkPatch};
51pub use pmem::{PmemConfig, PmemPatch};
52use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
53pub use serial::SerialConfig;
54pub use snapshot::{SnapshotCreateConfig, SnapshotLoadConfig};
55use squib_core::WireVmState;
56pub use vm::VmStateChange;
57pub use vsock::VsockConfig;
58
59#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
64pub struct VersionResponse {
65 pub firecracker_version: String,
67}
68
69#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
73pub struct InstanceInfo {
74 pub id: String,
76 pub state: VmState,
78 pub vmm_version: String,
80 pub app_name: String,
82}
83
84#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
91pub enum VmState {
92 #[default]
94 NotStarted,
95 Running,
97 Paused,
99}
100
101impl VmState {
102 #[must_use]
104 pub const fn as_wire_str(self) -> &'static str {
105 match self {
106 Self::NotStarted => "Not started",
107 Self::Running => "Running",
108 Self::Paused => "Paused",
109 }
110 }
111}
112
113impl From<WireVmState> for VmState {
114 fn from(s: WireVmState) -> Self {
115 match s {
116 WireVmState::NotStarted => Self::NotStarted,
117 WireVmState::Running => Self::Running,
118 WireVmState::Paused => Self::Paused,
119 }
120 }
121}
122
123impl Serialize for VmState {
124 fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
125 ser.serialize_str(self.as_wire_str())
126 }
127}
128
129impl<'de> Deserialize<'de> for VmState {
130 fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
131 let s = <&str>::deserialize(de)?;
132 match s {
133 "Not started" => Ok(Self::NotStarted),
134 "Running" => Ok(Self::Running),
135 "Paused" => Ok(Self::Paused),
136 other => Err(de::Error::unknown_variant(
137 other,
138 &["Not started", "Running", "Paused"],
139 )),
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_should_serialize_version_response_in_snake_case() {
150 let v = VersionResponse {
151 firecracker_version: "1.16.0".into(),
152 };
153 let json = serde_json::to_string(&v).unwrap();
154 assert_eq!(json, r#"{"firecracker_version":"1.16.0"}"#);
155 }
156
157 #[test]
158 fn test_should_round_trip_instance_info() {
159 let original = InstanceInfo {
160 id: "anonymous".into(),
161 state: VmState::NotStarted,
162 vmm_version: "1.16.0 (squib 0.1.0)".into(),
163 app_name: "Firecracker".into(),
164 };
165 let json = serde_json::to_string(&original).unwrap();
166 let back: InstanceInfo = serde_json::from_str(&json).unwrap();
167 assert_eq!(original, back);
168 }
169
170 #[test]
171 fn test_should_serialize_vm_state_to_upstream_strings_verbatim() {
172 assert_eq!(
173 serde_json::to_string(&VmState::NotStarted).unwrap(),
174 r#""Not started""#
175 );
176 assert_eq!(
177 serde_json::to_string(&VmState::Running).unwrap(),
178 r#""Running""#
179 );
180 assert_eq!(
181 serde_json::to_string(&VmState::Paused).unwrap(),
182 r#""Paused""#
183 );
184 }
185
186 #[test]
187 fn test_should_reject_pascalcase_vm_state_on_deserialize() {
188 let s: VmState = serde_json::from_str(r#""Not started""#).unwrap();
189 assert_eq!(s, VmState::NotStarted);
190
191 assert!(serde_json::from_str::<VmState>(r#""NotStarted""#).is_err());
194 }
195
196 #[test]
197 fn test_should_collapse_lifecycle_phase_to_vm_state() {
198 use squib_core::LifecyclePhase;
199
200 let cases = [
201 (LifecyclePhase::Uninitialized, VmState::NotStarted),
202 (LifecyclePhase::NotStarted, VmState::NotStarted),
203 (LifecyclePhase::Starting, VmState::NotStarted),
204 (LifecyclePhase::Shutdown, VmState::NotStarted),
205 (LifecyclePhase::Running, VmState::Running),
206 (LifecyclePhase::Paused, VmState::Paused),
207 ];
208 for (phase, expected) in cases {
209 assert_eq!(VmState::from(phase.wire_state()), expected, "{phase:?}");
210 }
211 }
212}