1use std::collections::HashSet;
9
10pub const SCHEMA_BASELINE_FORMAT_VERSION: u32 = 1;
16
17pub const DOWNGRADE_FLOOR: &str = "0.6.0";
19
20pub const MAINTENANCE_LEASE_MIGRATION_ID: &str = "m20260621_000002_create_maintenance_lease";
22
23pub const ACTIVE_CONFIG_MIGRATION_ID: &str = "m20260703_000001_add_sandbox_active_config";
25
26pub const SNAPSHOT_SCOPE_MIGRATION_ID: &str = "m20260714_000001_add_snapshot_scope";
28
29pub const SNAPSHOT_ARTIFACT_TRANSITION_MIGRATION_ID: &str =
31 "m20260723_000001_snapshot_artifact_transition";
32
33pub const CPU_ALLOCATION_MIGRATION_ID: &str = "m20260719_000001_create_cpu_allocations";
35
36pub const WRITEBACK_ALLOCATION_MIGRATION_ID: &str = "m20260803_000001_create_writeback_allocations";
38
39pub const MEMORY_ALLOCATION_NODES_MIGRATION_ID: &str =
41 "m20260808_000001_create_memory_allocation_nodes";
42
43pub const SANDBOX_LABEL_REBUILD_MIGRATION_ID: &str = "m20260810_000001_rebuild_sandbox_labels";
45
46pub const SHARED_CPU_ALLOCATION_MIGRATION_ID: &str = "m20260813_000001_share_cpu_allocations";
48pub const SANDBOX_NETWORK_SLOT_MIGRATION_ID: &str = "m20260818_000001_sandbox_network_slot";
50
51pub const MOUNT_OWNER_CONFIG_MIGRATION_ID: &str = "m20260824_000001_mount_owner_config";
53
54pub const BASELINE_0_6_0_MIGRATIONS: &[&str] = &[
61 "m20260305_000001_create_image_tables",
62 "m20260305_000002_create_sandbox_tables",
63 "m20260305_000003_create_storage_tables",
64 "m20260305_000004_create_sandbox_images_table",
65 "m20260410_000001_erofs_image_schema",
66 "m20260501_000001_create_snapshot_index",
67 "m20260517_000001_drop_sandbox_metric",
68 "m20260527_000001_migrate_oci_rootfs_source",
69 "m20260531_000001_create_sandbox_labels",
70 "m20260531_000002_index_sandbox_labels_key_value",
71 "m20260606_000001_named_volume_kinds",
72 "m20260621_000001_add_sandbox_ephemeral",
73 MAINTENANCE_LEASE_MIGRATION_ID,
74];
75
76pub const MIGRATION_METADATA: &[MigrationMetadata] = &[
78 MigrationMetadata {
79 id: "m20260305_000001_create_image_tables",
80 reversible: true,
81 affects_cache: true,
82 affects_user_data: false,
83 summary: "remove legacy OCI image catalog tables",
84 },
85 MigrationMetadata {
86 id: "m20260305_000002_create_sandbox_tables",
87 reversible: true,
88 affects_cache: false,
89 affects_user_data: false,
90 summary: "remove sandbox and run tables",
91 },
92 MigrationMetadata {
93 id: "m20260305_000003_create_storage_tables",
94 reversible: true,
95 affects_cache: false,
96 affects_user_data: false,
97 summary: "remove volume and snapshot storage tables",
98 },
99 MigrationMetadata {
100 id: "m20260305_000004_create_sandbox_images_table",
101 reversible: true,
102 affects_cache: true,
103 affects_user_data: false,
104 summary: "remove sandbox image references",
105 },
106 MigrationMetadata {
107 id: "m20260410_000001_erofs_image_schema",
108 reversible: true,
109 affects_cache: true,
110 affects_user_data: false,
111 summary: "remove EROFS rootfs catalog tables",
112 },
113 MigrationMetadata {
114 id: "m20260501_000001_create_snapshot_index",
115 reversible: true,
116 affects_cache: false,
117 affects_user_data: false,
118 summary: "remove snapshot index table",
119 },
120 MigrationMetadata {
121 id: "m20260517_000001_drop_sandbox_metric",
122 reversible: false,
123 affects_cache: false,
124 affects_user_data: false,
125 summary: "restore legacy sandbox metrics table",
126 },
127 MigrationMetadata {
128 id: "m20260527_000001_migrate_oci_rootfs_source",
129 reversible: false,
130 affects_cache: false,
131 affects_user_data: false,
132 summary: "rewrite OCI rootfs config back to the legacy string shape",
133 },
134 MigrationMetadata {
135 id: "m20260531_000001_create_sandbox_labels",
136 reversible: true,
137 affects_cache: false,
138 affects_user_data: false,
139 summary: "remove sandbox labels table",
140 },
141 MigrationMetadata {
142 id: "m20260531_000002_index_sandbox_labels_key_value",
143 reversible: true,
144 affects_cache: false,
145 affects_user_data: false,
146 summary: "remove sandbox label key/value index",
147 },
148 MigrationMetadata {
149 id: "m20260606_000001_named_volume_kinds",
150 reversible: true,
151 affects_cache: false,
152 affects_user_data: false,
153 summary: "remove named volume kind columns and attachments",
154 },
155 MigrationMetadata {
156 id: "m20260621_000001_add_sandbox_ephemeral",
157 reversible: true,
158 affects_cache: false,
159 affects_user_data: false,
160 summary: "remove sandbox ephemeral flag",
161 },
162 MigrationMetadata {
163 id: MAINTENANCE_LEASE_MIGRATION_ID,
164 reversible: true,
165 affects_cache: false,
166 affects_user_data: false,
167 summary: "remove maintenance lease table",
168 },
169 MigrationMetadata {
170 id: ACTIVE_CONFIG_MIGRATION_ID,
171 reversible: true,
172 affects_cache: false,
173 affects_user_data: false,
174 summary: "remove active sandbox config snapshots",
175 },
176 MigrationMetadata {
177 id: "m20260708_000001_migrate_bind_rootfs_source",
178 reversible: true,
179 affects_cache: false,
180 affects_user_data: true,
181 summary: "rewrite bind rootfs config back to the legacy string shape",
182 },
183 MigrationMetadata {
184 id: "m20260710_000001_migrate_root_disk",
185 reversible: true,
186 affects_cache: false,
187 affects_user_data: true,
188 summary: "rewrite root disk config back to the upper size shape",
189 },
190 MigrationMetadata {
191 id: SNAPSHOT_SCOPE_MIGRATION_ID,
192 reversible: true,
193 affects_cache: false,
194 affects_user_data: false,
195 summary: "remove snapshot scope index metadata",
196 },
197 MigrationMetadata {
198 id: SNAPSHOT_ARTIFACT_TRANSITION_MIGRATION_ID,
199 reversible: true,
200 affects_cache: false,
201 affects_user_data: true,
202 summary: "reverse final snapshot descriptors before removing migration state",
203 },
204 MigrationMetadata {
205 id: CPU_ALLOCATION_MIGRATION_ID,
206 reversible: true,
207 affects_cache: false,
208 affects_user_data: false,
209 summary: "remove cooperative host CPU allocation tables",
210 },
211 MigrationMetadata {
212 id: WRITEBACK_ALLOCATION_MIGRATION_ID,
213 reversible: true,
214 affects_cache: false,
215 affects_user_data: false,
216 summary: "remove host-global writeback allocation state",
217 },
218 MigrationMetadata {
219 id: MEMORY_ALLOCATION_NODES_MIGRATION_ID,
220 reversible: true,
221 affects_cache: false,
222 affects_user_data: false,
223 summary: "remove cooperative NUMA memory allocation state",
224 },
225 MigrationMetadata {
226 id: SANDBOX_LABEL_REBUILD_MIGRATION_ID,
227 reversible: true,
228 affects_cache: false,
229 affects_user_data: false,
230 summary: "retain the rebuilt sandbox label index",
231 },
232 MigrationMetadata {
233 id: SHARED_CPU_ALLOCATION_MIGRATION_ID,
234 reversible: true,
235 affects_cache: false,
236 affects_user_data: false,
237 summary: "restore exclusive logical CPU allocation rows",
238 },
239 MigrationMetadata {
240 id: MOUNT_OWNER_CONFIG_MIGRATION_ID,
241 reversible: true,
242 affects_cache: false,
243 affects_user_data: false,
244 summary: "remove the compatibility marker after confirming no persisted mount ownership",
245 },
246 MigrationMetadata {
247 id: SANDBOX_NETWORK_SLOT_MIGRATION_ID,
250 reversible: true,
254 affects_cache: false,
255 affects_user_data: false,
256 summary: "retain the compatible sandbox network slot column",
257 },
258];
259
260#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266pub struct MigrationMetadata {
267 pub id: &'static str,
269
270 pub reversible: bool,
272
273 pub affects_cache: bool,
276
277 pub affects_user_data: bool,
280
281 pub summary: &'static str,
283}
284
285pub fn migration_ids() -> impl Iterator<Item = &'static str> {
291 MIGRATION_METADATA.iter().map(|metadata| metadata.id)
292}
293
294pub fn canonical_applied_prefix<'a>(
300 applied_ids: impl IntoIterator<Item = &'a str>,
301) -> Option<&'static [MigrationMetadata]> {
302 let mut applied_count = 0;
303 let applied_ids: HashSet<_> = applied_ids
304 .into_iter()
305 .inspect(|_| applied_count += 1)
306 .collect();
307
308 if applied_ids.len() != applied_count {
310 return None;
311 }
312
313 let prefix = MIGRATION_METADATA.get(..applied_count)?;
314 prefix
315 .iter()
316 .all(|metadata| applied_ids.contains(metadata.id))
317 .then_some(prefix)
318}
319
320#[cfg(test)]
325mod tests {
326 use super::*;
327 use crate::{Migrator, MigratorTrait};
328
329 #[test]
330 fn metadata_matches_migrator_order() {
331 let migrations = Migrator::migrations();
332 let migrator_ids: Vec<_> = migrations
333 .iter()
334 .map(|migration| migration.name().to_string())
335 .collect();
336 let metadata_ids: Vec<_> = migration_ids().map(str::to_string).collect();
337
338 assert_eq!(metadata_ids, migrator_ids);
339 }
340
341 #[test]
342 fn canonical_applied_prefix_uses_metadata_order() {
343 let applied = [
344 MOUNT_OWNER_CONFIG_MIGRATION_ID,
345 SANDBOX_NETWORK_SLOT_MIGRATION_ID,
346 SHARED_CPU_ALLOCATION_MIGRATION_ID,
347 SANDBOX_LABEL_REBUILD_MIGRATION_ID,
348 MEMORY_ALLOCATION_NODES_MIGRATION_ID,
349 WRITEBACK_ALLOCATION_MIGRATION_ID,
350 SNAPSHOT_ARTIFACT_TRANSITION_MIGRATION_ID,
351 CPU_ALLOCATION_MIGRATION_ID,
352 ];
353 let prefix_len = MIGRATION_METADATA.len();
354 let mut all_applied: Vec<_> = MIGRATION_METADATA[..prefix_len - applied.len()]
355 .iter()
356 .map(|metadata| metadata.id)
357 .collect();
358 all_applied.extend(applied);
359
360 let prefix = canonical_applied_prefix(all_applied).expect("valid unordered prefix");
361 assert_eq!(prefix, MIGRATION_METADATA);
362 }
363
364 #[test]
365 fn canonical_applied_prefix_rejects_gaps_and_unknown_migrations() {
366 let without_first = MIGRATION_METADATA
367 .iter()
368 .skip(1)
369 .map(|metadata| metadata.id);
370 assert!(canonical_applied_prefix(without_first).is_none());
371
372 let with_unknown = MIGRATION_METADATA
373 .iter()
374 .map(|metadata| metadata.id)
375 .chain(["m20990101_000001_future"]);
376 assert!(canonical_applied_prefix(with_unknown).is_none());
377 }
378
379 #[test]
380 fn released_v0_6_15_migrations_remain_a_prefix() {
381 let applied: Vec<_> = migration_ids()
382 .take_while(|id| *id != SANDBOX_NETWORK_SLOT_MIGRATION_ID)
383 .collect();
384
385 assert_eq!(applied.last(), Some(&MOUNT_OWNER_CONFIG_MIGRATION_ID));
386 assert!(canonical_applied_prefix(applied).is_some());
387 }
388
389 #[test]
390 fn frozen_0_6_0_baseline_is_current_prefix() {
391 let metadata_ids: Vec<_> = migration_ids().collect();
392 assert!(metadata_ids.starts_with(BASELINE_0_6_0_MIGRATIONS));
393 }
394}