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 SNAPSHOT_IDENTITY_MIGRATION_ID: &str = "m20260829_000001_split_snapshot_identity";
56
57pub const SNAPSHOT_GROUPS_MIGRATION_ID: &str = "m20260910_000001_snapshot_groups";
59
60pub const SECRET_CONFIG_MIGRATION_ID: &str = "m20260922_000001_migrate_secret_config";
62
63pub const BASELINE_0_6_0_MIGRATIONS: &[&str] = &[
70 "m20260305_000001_create_image_tables",
71 "m20260305_000002_create_sandbox_tables",
72 "m20260305_000003_create_storage_tables",
73 "m20260305_000004_create_sandbox_images_table",
74 "m20260410_000001_erofs_image_schema",
75 "m20260501_000001_create_snapshot_index",
76 "m20260517_000001_drop_sandbox_metric",
77 "m20260527_000001_migrate_oci_rootfs_source",
78 "m20260531_000001_create_sandbox_labels",
79 "m20260531_000002_index_sandbox_labels_key_value",
80 "m20260606_000001_named_volume_kinds",
81 "m20260621_000001_add_sandbox_ephemeral",
82 MAINTENANCE_LEASE_MIGRATION_ID,
83];
84
85pub const MIGRATION_METADATA: &[MigrationMetadata] = &[
87 MigrationMetadata {
88 id: "m20260305_000001_create_image_tables",
89 reversible: true,
90 affects_cache: true,
91 affects_user_data: false,
92 summary: "remove legacy OCI image catalog tables",
93 },
94 MigrationMetadata {
95 id: "m20260305_000002_create_sandbox_tables",
96 reversible: true,
97 affects_cache: false,
98 affects_user_data: false,
99 summary: "remove sandbox and run tables",
100 },
101 MigrationMetadata {
102 id: "m20260305_000003_create_storage_tables",
103 reversible: true,
104 affects_cache: false,
105 affects_user_data: false,
106 summary: "remove volume and snapshot storage tables",
107 },
108 MigrationMetadata {
109 id: "m20260305_000004_create_sandbox_images_table",
110 reversible: true,
111 affects_cache: true,
112 affects_user_data: false,
113 summary: "remove sandbox image references",
114 },
115 MigrationMetadata {
116 id: "m20260410_000001_erofs_image_schema",
117 reversible: true,
118 affects_cache: true,
119 affects_user_data: false,
120 summary: "remove EROFS rootfs catalog tables",
121 },
122 MigrationMetadata {
123 id: "m20260501_000001_create_snapshot_index",
124 reversible: true,
125 affects_cache: false,
126 affects_user_data: false,
127 summary: "remove snapshot index table",
128 },
129 MigrationMetadata {
130 id: "m20260517_000001_drop_sandbox_metric",
131 reversible: false,
132 affects_cache: false,
133 affects_user_data: false,
134 summary: "restore legacy sandbox metrics table",
135 },
136 MigrationMetadata {
137 id: "m20260527_000001_migrate_oci_rootfs_source",
138 reversible: false,
139 affects_cache: false,
140 affects_user_data: false,
141 summary: "rewrite OCI rootfs config back to the legacy string shape",
142 },
143 MigrationMetadata {
144 id: "m20260531_000001_create_sandbox_labels",
145 reversible: true,
146 affects_cache: false,
147 affects_user_data: false,
148 summary: "remove sandbox labels table",
149 },
150 MigrationMetadata {
151 id: "m20260531_000002_index_sandbox_labels_key_value",
152 reversible: true,
153 affects_cache: false,
154 affects_user_data: false,
155 summary: "remove sandbox label key/value index",
156 },
157 MigrationMetadata {
158 id: "m20260606_000001_named_volume_kinds",
159 reversible: true,
160 affects_cache: false,
161 affects_user_data: false,
162 summary: "remove named volume kind columns and attachments",
163 },
164 MigrationMetadata {
165 id: "m20260621_000001_add_sandbox_ephemeral",
166 reversible: true,
167 affects_cache: false,
168 affects_user_data: false,
169 summary: "remove sandbox ephemeral flag",
170 },
171 MigrationMetadata {
172 id: MAINTENANCE_LEASE_MIGRATION_ID,
173 reversible: true,
174 affects_cache: false,
175 affects_user_data: false,
176 summary: "remove maintenance lease table",
177 },
178 MigrationMetadata {
179 id: ACTIVE_CONFIG_MIGRATION_ID,
180 reversible: true,
181 affects_cache: false,
182 affects_user_data: false,
183 summary: "remove active sandbox config snapshots",
184 },
185 MigrationMetadata {
186 id: "m20260708_000001_migrate_bind_rootfs_source",
187 reversible: true,
188 affects_cache: false,
189 affects_user_data: true,
190 summary: "rewrite bind rootfs config back to the legacy string shape",
191 },
192 MigrationMetadata {
193 id: "m20260710_000001_migrate_root_disk",
194 reversible: true,
195 affects_cache: false,
196 affects_user_data: true,
197 summary: "rewrite root disk config back to the upper size shape",
198 },
199 MigrationMetadata {
200 id: SNAPSHOT_SCOPE_MIGRATION_ID,
201 reversible: true,
202 affects_cache: false,
203 affects_user_data: false,
204 summary: "remove snapshot scope index metadata",
205 },
206 MigrationMetadata {
207 id: SNAPSHOT_ARTIFACT_TRANSITION_MIGRATION_ID,
208 reversible: true,
209 affects_cache: false,
210 affects_user_data: true,
211 summary: "reverse final snapshot descriptors before removing migration state",
212 },
213 MigrationMetadata {
214 id: CPU_ALLOCATION_MIGRATION_ID,
215 reversible: true,
216 affects_cache: false,
217 affects_user_data: false,
218 summary: "remove cooperative host CPU allocation tables",
219 },
220 MigrationMetadata {
221 id: WRITEBACK_ALLOCATION_MIGRATION_ID,
222 reversible: true,
223 affects_cache: false,
224 affects_user_data: false,
225 summary: "remove host-global writeback allocation state",
226 },
227 MigrationMetadata {
228 id: MEMORY_ALLOCATION_NODES_MIGRATION_ID,
229 reversible: true,
230 affects_cache: false,
231 affects_user_data: false,
232 summary: "remove cooperative NUMA memory allocation state",
233 },
234 MigrationMetadata {
235 id: SANDBOX_LABEL_REBUILD_MIGRATION_ID,
236 reversible: true,
237 affects_cache: false,
238 affects_user_data: false,
239 summary: "retain the rebuilt sandbox label index",
240 },
241 MigrationMetadata {
242 id: SHARED_CPU_ALLOCATION_MIGRATION_ID,
243 reversible: true,
244 affects_cache: false,
245 affects_user_data: false,
246 summary: "restore exclusive logical CPU allocation rows",
247 },
248 MigrationMetadata {
249 id: MOUNT_OWNER_CONFIG_MIGRATION_ID,
250 reversible: true,
251 affects_cache: false,
252 affects_user_data: false,
253 summary: "remove the compatibility marker after confirming no persisted mount ownership",
254 },
255 MigrationMetadata {
256 id: SANDBOX_NETWORK_SLOT_MIGRATION_ID,
259 reversible: true,
263 affects_cache: false,
264 affects_user_data: false,
265 summary: "retain the compatible sandbox network slot column",
266 },
267 MigrationMetadata {
268 id: SNAPSHOT_IDENTITY_MIGRATION_ID,
269 reversible: true,
270 affects_cache: true,
271 affects_user_data: true,
272 summary: "reverse final snapshot descriptors before dropping identity projections",
273 },
274 MigrationMetadata {
275 id: SNAPSHOT_GROUPS_MIGRATION_ID,
276 reversible: true,
277 affects_cache: false,
278 affects_user_data: true,
279 summary: "restore the flat snapshot index only when no groups or duplicate identities remain",
280 },
281 MigrationMetadata {
282 id: SECRET_CONFIG_MIGRATION_ID,
283 reversible: true,
284 affects_cache: false,
285 affects_user_data: false,
286 summary: "retain secret policies only when the target can represent them",
287 },
288];
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub struct MigrationMetadata {
297 pub id: &'static str,
299
300 pub reversible: bool,
302
303 pub affects_cache: bool,
306
307 pub affects_user_data: bool,
310
311 pub summary: &'static str,
313}
314
315pub fn migration_ids() -> impl Iterator<Item = &'static str> {
321 MIGRATION_METADATA.iter().map(|metadata| metadata.id)
322}
323
324pub fn canonical_applied_prefix<'a>(
330 applied_ids: impl IntoIterator<Item = &'a str>,
331) -> Option<&'static [MigrationMetadata]> {
332 let mut applied_count = 0;
333 let applied_ids: HashSet<_> = applied_ids
334 .into_iter()
335 .inspect(|_| applied_count += 1)
336 .collect();
337
338 if applied_ids.len() != applied_count {
340 return None;
341 }
342
343 let prefix = MIGRATION_METADATA.get(..applied_count)?;
344 prefix
345 .iter()
346 .all(|metadata| applied_ids.contains(metadata.id))
347 .then_some(prefix)
348}
349
350#[cfg(test)]
355mod tests {
356 use super::*;
357 use crate::{Migrator, MigratorTrait};
358
359 #[test]
360 fn metadata_matches_migrator_order() {
361 let migrations = Migrator::migrations();
362 let migrator_ids: Vec<_> = migrations
363 .iter()
364 .map(|migration| migration.name().to_string())
365 .collect();
366 let metadata_ids: Vec<_> = migration_ids().map(str::to_string).collect();
367
368 assert_eq!(metadata_ids, migrator_ids);
369 }
370
371 #[test]
372 fn canonical_applied_prefix_uses_metadata_order() {
373 let applied = [
374 SECRET_CONFIG_MIGRATION_ID,
375 SNAPSHOT_GROUPS_MIGRATION_ID,
376 SNAPSHOT_IDENTITY_MIGRATION_ID,
377 MOUNT_OWNER_CONFIG_MIGRATION_ID,
378 SANDBOX_NETWORK_SLOT_MIGRATION_ID,
379 SHARED_CPU_ALLOCATION_MIGRATION_ID,
380 SANDBOX_LABEL_REBUILD_MIGRATION_ID,
381 MEMORY_ALLOCATION_NODES_MIGRATION_ID,
382 WRITEBACK_ALLOCATION_MIGRATION_ID,
383 SNAPSHOT_ARTIFACT_TRANSITION_MIGRATION_ID,
384 CPU_ALLOCATION_MIGRATION_ID,
385 ];
386 let prefix_len = MIGRATION_METADATA.len();
387 let mut all_applied: Vec<_> = MIGRATION_METADATA[..prefix_len - applied.len()]
388 .iter()
389 .map(|metadata| metadata.id)
390 .collect();
391 all_applied.extend(applied);
392
393 let prefix = canonical_applied_prefix(all_applied).expect("valid unordered prefix");
394 assert_eq!(prefix, MIGRATION_METADATA);
395 }
396
397 #[test]
398 fn canonical_applied_prefix_rejects_gaps_and_unknown_migrations() {
399 let without_first = MIGRATION_METADATA
400 .iter()
401 .skip(1)
402 .map(|metadata| metadata.id);
403 assert!(canonical_applied_prefix(without_first).is_none());
404
405 let with_unknown = MIGRATION_METADATA
406 .iter()
407 .map(|metadata| metadata.id)
408 .chain(["m20990101_000001_future"]);
409 assert!(canonical_applied_prefix(with_unknown).is_none());
410 }
411
412 #[test]
413 fn released_v0_6_15_migrations_remain_a_prefix() {
414 let applied: Vec<_> = migration_ids()
415 .take_while(|id| *id != SANDBOX_NETWORK_SLOT_MIGRATION_ID)
416 .collect();
417
418 assert_eq!(applied.last(), Some(&MOUNT_OWNER_CONFIG_MIGRATION_ID));
419 assert!(canonical_applied_prefix(applied).is_some());
420 }
421
422 #[test]
423 fn snapshot_stack_follows_released_v0_6_18_prefix() {
424 let released = [
428 "m20260305_000001_create_image_tables",
429 "m20260305_000002_create_sandbox_tables",
430 "m20260305_000003_create_storage_tables",
431 "m20260305_000004_create_sandbox_images_table",
432 "m20260410_000001_erofs_image_schema",
433 "m20260501_000001_create_snapshot_index",
434 "m20260517_000001_drop_sandbox_metric",
435 "m20260527_000001_migrate_oci_rootfs_source",
436 "m20260531_000001_create_sandbox_labels",
437 "m20260531_000002_index_sandbox_labels_key_value",
438 "m20260606_000001_named_volume_kinds",
439 "m20260621_000001_add_sandbox_ephemeral",
440 "m20260621_000002_create_maintenance_lease",
441 "m20260703_000001_add_sandbox_active_config",
442 "m20260708_000001_migrate_bind_rootfs_source",
443 "m20260710_000001_migrate_root_disk",
444 "m20260714_000001_add_snapshot_scope",
445 "m20260723_000001_snapshot_artifact_transition",
446 "m20260719_000001_create_cpu_allocations",
447 "m20260803_000001_create_writeback_allocations",
448 "m20260808_000001_create_memory_allocation_nodes",
449 "m20260810_000001_rebuild_sandbox_labels",
450 "m20260813_000001_share_cpu_allocations",
451 "m20260824_000001_mount_owner_config",
452 "m20260818_000001_sandbox_network_slot",
453 ];
454 let current: Vec<_> = migration_ids().collect();
455 assert!(current.starts_with(&released));
456 assert_eq!(
457 ¤t[released.len()..],
458 &[
459 SNAPSHOT_IDENTITY_MIGRATION_ID,
460 SNAPSHOT_GROUPS_MIGRATION_ID,
461 SECRET_CONFIG_MIGRATION_ID
462 ],
463 );
464 assert!(canonical_applied_prefix(released).is_some());
465 }
466
467 #[test]
468 fn frozen_0_6_0_baseline_is_current_prefix() {
469 let metadata_ids: Vec<_> = migration_ids().collect();
470 assert!(metadata_ids.starts_with(BASELINE_0_6_0_MIGRATIONS));
471 }
472}