openvmm_helpers/
snapshot.rs1use mesh::payload::Protobuf;
7use mesh::payload::Timestamp;
8
9pub mod publish;
10pub mod restore;
11
12pub use publish::write_snapshot;
13pub use restore::read_snapshot;
14
15pub const MANIFEST_VERSION: u32 = 1;
17
18#[derive(Clone, Protobuf)]
20#[mesh(package = "openvmm.snapshot")]
21pub struct SnapshotManifest {
22 #[mesh(1)]
24 pub version: u32,
25 #[mesh(2)]
27 pub created_at: Timestamp,
28 #[mesh(3)]
30 pub openvmm_version: String,
31 #[mesh(4)]
33 pub memory_size_bytes: u64,
34 #[mesh(5)]
36 pub vp_count: u32,
37 #[mesh(6)]
39 pub page_size: u32,
40 #[mesh(7)]
42 pub architecture: String,
43}
44
45pub fn validate_manifest(
51 manifest: &SnapshotManifest,
52 expected_arch: &str,
53 expected_memory_size: u64,
54 expected_vp_count: u32,
55 expected_page_size: u32,
56) -> anyhow::Result<()> {
57 if manifest.version != MANIFEST_VERSION {
58 anyhow::bail!(
59 "snapshot manifest version {} is not supported (expected {})",
60 manifest.version,
61 MANIFEST_VERSION,
62 );
63 }
64
65 if manifest.architecture != expected_arch {
66 anyhow::bail!(
67 "snapshot architecture '{}' doesn't match expected '{}'",
68 manifest.architecture,
69 expected_arch,
70 );
71 }
72
73 if manifest.memory_size_bytes != expected_memory_size {
74 anyhow::bail!(
75 "snapshot memory size ({} bytes) doesn't match expected ({} bytes)",
76 manifest.memory_size_bytes,
77 expected_memory_size,
78 );
79 }
80
81 if manifest.vp_count != expected_vp_count {
82 anyhow::bail!(
83 "snapshot VP count ({}) doesn't match expected ({})",
84 manifest.vp_count,
85 expected_vp_count,
86 );
87 }
88
89 if manifest.page_size != expected_page_size {
90 anyhow::bail!(
91 "snapshot page size ({}) doesn't match expected ({})",
92 manifest.page_size,
93 expected_page_size,
94 );
95 }
96
97 Ok(())
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 fn test_manifest() -> SnapshotManifest {
106 SnapshotManifest {
107 version: MANIFEST_VERSION,
108 created_at: Timestamp {
109 seconds: 1234567890,
110 nanos: 0,
111 },
112 openvmm_version: "test-0.1.0".to_string(),
113 memory_size_bytes: 1024,
114 vp_count: 2,
115 page_size: 4096,
116 architecture: "x86_64".to_string(),
117 }
118 }
119
120 #[test]
121 fn write_read_roundtrip() {
122 let dir = tempfile::tempdir().unwrap();
123 let snap_dir = dir.path().join("snap");
124
125 let mem_path = dir.path().join("memory.bin");
127 std::fs::write(&mem_path, b"FAKEMEM").unwrap();
128
129 let manifest = test_manifest();
130 let state = b"saved-state-data";
131
132 write_snapshot(&snap_dir, &manifest, state, &mem_path).unwrap();
133
134 let (read_manifest, read_state) = read_snapshot(&snap_dir).unwrap();
135 assert_eq!(read_manifest.version, manifest.version);
136 assert_eq!(read_manifest.memory_size_bytes, manifest.memory_size_bytes);
137 assert_eq!(read_manifest.vp_count, manifest.vp_count);
138 assert_eq!(read_manifest.architecture, manifest.architecture);
139 assert_eq!(read_state, state);
140
141 assert!(snap_dir.join("memory.bin").exists());
143 }
144
145 #[test]
146 fn write_snapshot_creates_dir() {
147 let dir = tempfile::tempdir().unwrap();
148 let snap_dir = dir.path().join("a").join("b").join("c");
149
150 let mem_path = dir.path().join("memory.bin");
151 std::fs::write(&mem_path, b"MEM").unwrap();
152
153 write_snapshot(&snap_dir, &test_manifest(), b"state", &mem_path).unwrap();
154
155 assert!(snap_dir.join("manifest.bin").exists());
156 assert!(snap_dir.join("state.bin").exists());
157 assert!(snap_dir.join("memory.bin").exists());
158 }
159
160 #[test]
161 fn write_snapshot_same_memory_path() {
162 let dir = tempfile::tempdir().unwrap();
165 let snap_dir = dir.path().join("snap");
166 std::fs::create_dir_all(&snap_dir).unwrap();
167
168 let mem_path = snap_dir.join("memory.bin");
169 std::fs::write(&mem_path, b"SAMEFILE").unwrap();
170
171 write_snapshot(&snap_dir, &test_manifest(), b"state", &mem_path).unwrap();
173
174 assert_eq!(std::fs::read(&mem_path).unwrap(), b"SAMEFILE");
176 }
177
178 #[test]
179 fn read_snapshot_missing_file() {
180 let dir = tempfile::tempdir().unwrap();
181 let result = read_snapshot(dir.path());
183 assert!(result.is_err());
184 }
185
186 #[test]
187 fn validate_manifest_ok() {
188 let manifest = test_manifest();
189 validate_manifest(&manifest, "x86_64", 1024, 2, 4096).unwrap();
190 }
191
192 #[test]
193 fn validate_manifest_wrong_arch() {
194 let manifest = test_manifest();
195 let err = validate_manifest(&manifest, "aarch64", 1024, 2, 4096).unwrap_err();
196 assert!(
197 err.to_string().contains("architecture"),
198 "unexpected error: {err}"
199 );
200 }
201
202 #[test]
203 fn validate_manifest_wrong_memory_size() {
204 let manifest = test_manifest();
205 let err = validate_manifest(&manifest, "x86_64", 9999, 2, 4096).unwrap_err();
206 assert!(
207 err.to_string().contains("memory size"),
208 "unexpected error: {err}"
209 );
210 }
211
212 #[test]
213 fn validate_manifest_wrong_vp_count() {
214 let manifest = test_manifest();
215 let err = validate_manifest(&manifest, "x86_64", 1024, 99, 4096).unwrap_err();
216 assert!(
217 err.to_string().contains("VP count"),
218 "unexpected error: {err}"
219 );
220 }
221
222 #[test]
223 fn validate_manifest_wrong_page_size() {
224 let manifest = test_manifest();
225 let err = validate_manifest(&manifest, "x86_64", 1024, 2, 65536).unwrap_err();
226 assert!(
227 err.to_string().contains("page size"),
228 "unexpected error: {err}"
229 );
230 }
231
232 #[test]
233 fn validate_manifest_wrong_version() {
234 let mut manifest = test_manifest();
235 manifest.version = 999;
236 let err = validate_manifest(&manifest, "x86_64", 1024, 2, 4096).unwrap_err();
237 assert!(
238 err.to_string().contains("version"),
239 "unexpected error: {err}"
240 );
241 }
242}