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: SANDBOX_NETWORK_SLOT_MIGRATION_ID,
241 reversible: true,
245 affects_cache: false,
246 affects_user_data: false,
247 summary: "retain the compatible sandbox network slot column",
248 },
249 MigrationMetadata {
250 id: MOUNT_OWNER_CONFIG_MIGRATION_ID,
251 reversible: true,
252 affects_cache: false,
253 affects_user_data: false,
254 summary: "remove the compatibility marker after confirming no persisted mount ownership",
255 },
256];
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
264pub struct MigrationMetadata {
265 pub id: &'static str,
267
268 pub reversible: bool,
270
271 pub affects_cache: bool,
274
275 pub affects_user_data: bool,
278
279 pub summary: &'static str,
281}
282
283pub fn migration_ids() -> impl Iterator<Item = &'static str> {
289 MIGRATION_METADATA.iter().map(|metadata| metadata.id)
290}
291
292pub fn canonical_applied_prefix<'a>(
298 applied_ids: impl IntoIterator<Item = &'a str>,
299) -> Option<&'static [MigrationMetadata]> {
300 let mut applied_count = 0;
301 let applied_ids: HashSet<_> = applied_ids
302 .into_iter()
303 .inspect(|_| applied_count += 1)
304 .collect();
305
306 if applied_ids.len() != applied_count {
308 return None;
309 }
310
311 let prefix = MIGRATION_METADATA.get(..applied_count)?;
312 prefix
313 .iter()
314 .all(|metadata| applied_ids.contains(metadata.id))
315 .then_some(prefix)
316}
317
318#[cfg(test)]
323mod tests {
324 use super::*;
325 use crate::{Migrator, MigratorTrait};
326
327 #[test]
328 fn metadata_matches_migrator_order() {
329 let migrations = Migrator::migrations();
330 let migrator_ids: Vec<_> = migrations
331 .iter()
332 .map(|migration| migration.name().to_string())
333 .collect();
334 let metadata_ids: Vec<_> = migration_ids().map(str::to_string).collect();
335
336 assert_eq!(metadata_ids, migrator_ids);
337 }
338
339 #[test]
340 fn canonical_applied_prefix_uses_metadata_order() {
341 let applied = [
342 MOUNT_OWNER_CONFIG_MIGRATION_ID,
343 SANDBOX_NETWORK_SLOT_MIGRATION_ID,
344 SHARED_CPU_ALLOCATION_MIGRATION_ID,
345 SANDBOX_LABEL_REBUILD_MIGRATION_ID,
346 MEMORY_ALLOCATION_NODES_MIGRATION_ID,
347 WRITEBACK_ALLOCATION_MIGRATION_ID,
348 SNAPSHOT_ARTIFACT_TRANSITION_MIGRATION_ID,
349 CPU_ALLOCATION_MIGRATION_ID,
350 ];
351 let prefix_len = MIGRATION_METADATA.len();
352 let mut all_applied: Vec<_> = MIGRATION_METADATA[..prefix_len - applied.len()]
353 .iter()
354 .map(|metadata| metadata.id)
355 .collect();
356 all_applied.extend(applied);
357
358 let prefix = canonical_applied_prefix(all_applied).expect("valid unordered prefix");
359 assert_eq!(prefix, MIGRATION_METADATA);
360 }
361
362 #[test]
363 fn canonical_applied_prefix_rejects_gaps_and_unknown_migrations() {
364 let without_first = MIGRATION_METADATA
365 .iter()
366 .skip(1)
367 .map(|metadata| metadata.id);
368 assert!(canonical_applied_prefix(without_first).is_none());
369
370 let with_unknown = MIGRATION_METADATA
371 .iter()
372 .map(|metadata| metadata.id)
373 .chain(["m20990101_000001_future"]);
374 assert!(canonical_applied_prefix(with_unknown).is_none());
375 }
376
377 #[test]
378 fn frozen_0_6_0_baseline_is_current_prefix() {
379 let metadata_ids: Vec<_> = migration_ids().collect();
380 assert!(metadata_ids.starts_with(BASELINE_0_6_0_MIGRATIONS));
381 }
382}