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";
48
49pub const MOUNT_OWNER_CONFIG_MIGRATION_ID: &str = "m20260824_000001_mount_owner_config";
51
52pub const BASELINE_0_6_0_MIGRATIONS: &[&str] = &[
59 "m20260305_000001_create_image_tables",
60 "m20260305_000002_create_sandbox_tables",
61 "m20260305_000003_create_storage_tables",
62 "m20260305_000004_create_sandbox_images_table",
63 "m20260410_000001_erofs_image_schema",
64 "m20260501_000001_create_snapshot_index",
65 "m20260517_000001_drop_sandbox_metric",
66 "m20260527_000001_migrate_oci_rootfs_source",
67 "m20260531_000001_create_sandbox_labels",
68 "m20260531_000002_index_sandbox_labels_key_value",
69 "m20260606_000001_named_volume_kinds",
70 "m20260621_000001_add_sandbox_ephemeral",
71 MAINTENANCE_LEASE_MIGRATION_ID,
72];
73
74pub const MIGRATION_METADATA: &[MigrationMetadata] = &[
76 MigrationMetadata {
77 id: "m20260305_000001_create_image_tables",
78 reversible: true,
79 affects_cache: true,
80 affects_user_data: false,
81 summary: "remove legacy OCI image catalog tables",
82 },
83 MigrationMetadata {
84 id: "m20260305_000002_create_sandbox_tables",
85 reversible: true,
86 affects_cache: false,
87 affects_user_data: false,
88 summary: "remove sandbox and run tables",
89 },
90 MigrationMetadata {
91 id: "m20260305_000003_create_storage_tables",
92 reversible: true,
93 affects_cache: false,
94 affects_user_data: false,
95 summary: "remove volume and snapshot storage tables",
96 },
97 MigrationMetadata {
98 id: "m20260305_000004_create_sandbox_images_table",
99 reversible: true,
100 affects_cache: true,
101 affects_user_data: false,
102 summary: "remove sandbox image references",
103 },
104 MigrationMetadata {
105 id: "m20260410_000001_erofs_image_schema",
106 reversible: true,
107 affects_cache: true,
108 affects_user_data: false,
109 summary: "remove EROFS rootfs catalog tables",
110 },
111 MigrationMetadata {
112 id: "m20260501_000001_create_snapshot_index",
113 reversible: true,
114 affects_cache: false,
115 affects_user_data: false,
116 summary: "remove snapshot index table",
117 },
118 MigrationMetadata {
119 id: "m20260517_000001_drop_sandbox_metric",
120 reversible: false,
121 affects_cache: false,
122 affects_user_data: false,
123 summary: "restore legacy sandbox metrics table",
124 },
125 MigrationMetadata {
126 id: "m20260527_000001_migrate_oci_rootfs_source",
127 reversible: false,
128 affects_cache: false,
129 affects_user_data: false,
130 summary: "rewrite OCI rootfs config back to the legacy string shape",
131 },
132 MigrationMetadata {
133 id: "m20260531_000001_create_sandbox_labels",
134 reversible: true,
135 affects_cache: false,
136 affects_user_data: false,
137 summary: "remove sandbox labels table",
138 },
139 MigrationMetadata {
140 id: "m20260531_000002_index_sandbox_labels_key_value",
141 reversible: true,
142 affects_cache: false,
143 affects_user_data: false,
144 summary: "remove sandbox label key/value index",
145 },
146 MigrationMetadata {
147 id: "m20260606_000001_named_volume_kinds",
148 reversible: true,
149 affects_cache: false,
150 affects_user_data: false,
151 summary: "remove named volume kind columns and attachments",
152 },
153 MigrationMetadata {
154 id: "m20260621_000001_add_sandbox_ephemeral",
155 reversible: true,
156 affects_cache: false,
157 affects_user_data: false,
158 summary: "remove sandbox ephemeral flag",
159 },
160 MigrationMetadata {
161 id: MAINTENANCE_LEASE_MIGRATION_ID,
162 reversible: true,
163 affects_cache: false,
164 affects_user_data: false,
165 summary: "remove maintenance lease table",
166 },
167 MigrationMetadata {
168 id: ACTIVE_CONFIG_MIGRATION_ID,
169 reversible: true,
170 affects_cache: false,
171 affects_user_data: false,
172 summary: "remove active sandbox config snapshots",
173 },
174 MigrationMetadata {
175 id: "m20260708_000001_migrate_bind_rootfs_source",
176 reversible: true,
177 affects_cache: false,
178 affects_user_data: true,
179 summary: "rewrite bind rootfs config back to the legacy string shape",
180 },
181 MigrationMetadata {
182 id: "m20260710_000001_migrate_root_disk",
183 reversible: true,
184 affects_cache: false,
185 affects_user_data: true,
186 summary: "rewrite root disk config back to the upper size shape",
187 },
188 MigrationMetadata {
189 id: SNAPSHOT_SCOPE_MIGRATION_ID,
190 reversible: true,
191 affects_cache: false,
192 affects_user_data: false,
193 summary: "remove snapshot scope index metadata",
194 },
195 MigrationMetadata {
196 id: SNAPSHOT_ARTIFACT_TRANSITION_MIGRATION_ID,
197 reversible: true,
198 affects_cache: false,
199 affects_user_data: true,
200 summary: "reverse final snapshot descriptors before removing migration state",
201 },
202 MigrationMetadata {
203 id: CPU_ALLOCATION_MIGRATION_ID,
204 reversible: true,
205 affects_cache: false,
206 affects_user_data: false,
207 summary: "remove cooperative host CPU allocation tables",
208 },
209 MigrationMetadata {
210 id: WRITEBACK_ALLOCATION_MIGRATION_ID,
211 reversible: true,
212 affects_cache: false,
213 affects_user_data: false,
214 summary: "remove host-global writeback allocation state",
215 },
216 MigrationMetadata {
217 id: MEMORY_ALLOCATION_NODES_MIGRATION_ID,
218 reversible: true,
219 affects_cache: false,
220 affects_user_data: false,
221 summary: "remove cooperative NUMA memory allocation state",
222 },
223 MigrationMetadata {
224 id: SANDBOX_LABEL_REBUILD_MIGRATION_ID,
225 reversible: true,
226 affects_cache: false,
227 affects_user_data: false,
228 summary: "retain the rebuilt sandbox label index",
229 },
230 MigrationMetadata {
231 id: SHARED_CPU_ALLOCATION_MIGRATION_ID,
232 reversible: true,
233 affects_cache: false,
234 affects_user_data: false,
235 summary: "restore exclusive logical CPU allocation rows",
236 },
237 MigrationMetadata {
238 id: MOUNT_OWNER_CONFIG_MIGRATION_ID,
239 reversible: true,
240 affects_cache: false,
241 affects_user_data: false,
242 summary: "remove the compatibility marker after confirming no persisted mount ownership",
243 },
244];
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252pub struct MigrationMetadata {
253 pub id: &'static str,
255
256 pub reversible: bool,
258
259 pub affects_cache: bool,
262
263 pub affects_user_data: bool,
266
267 pub summary: &'static str,
269}
270
271pub fn migration_ids() -> impl Iterator<Item = &'static str> {
277 MIGRATION_METADATA.iter().map(|metadata| metadata.id)
278}
279
280pub fn canonical_applied_prefix<'a>(
286 applied_ids: impl IntoIterator<Item = &'a str>,
287) -> Option<&'static [MigrationMetadata]> {
288 let mut applied_count = 0;
289 let applied_ids: HashSet<_> = applied_ids
290 .into_iter()
291 .inspect(|_| applied_count += 1)
292 .collect();
293
294 if applied_ids.len() != applied_count {
296 return None;
297 }
298
299 let prefix = MIGRATION_METADATA.get(..applied_count)?;
300 prefix
301 .iter()
302 .all(|metadata| applied_ids.contains(metadata.id))
303 .then_some(prefix)
304}
305
306#[cfg(test)]
311mod tests {
312 use super::*;
313 use crate::{Migrator, MigratorTrait};
314
315 #[test]
316 fn metadata_matches_migrator_order() {
317 let migrations = Migrator::migrations();
318 let migrator_ids: Vec<_> = migrations
319 .iter()
320 .map(|migration| migration.name().to_string())
321 .collect();
322 let metadata_ids: Vec<_> = migration_ids().map(str::to_string).collect();
323
324 assert_eq!(metadata_ids, migrator_ids);
325 }
326
327 #[test]
328 fn canonical_applied_prefix_uses_metadata_order() {
329 let applied = [
330 MOUNT_OWNER_CONFIG_MIGRATION_ID,
331 SHARED_CPU_ALLOCATION_MIGRATION_ID,
332 SANDBOX_LABEL_REBUILD_MIGRATION_ID,
333 MEMORY_ALLOCATION_NODES_MIGRATION_ID,
334 WRITEBACK_ALLOCATION_MIGRATION_ID,
335 SNAPSHOT_ARTIFACT_TRANSITION_MIGRATION_ID,
336 CPU_ALLOCATION_MIGRATION_ID,
337 ];
338 let prefix_len = MIGRATION_METADATA.len();
339 let mut all_applied: Vec<_> = MIGRATION_METADATA[..prefix_len - applied.len()]
340 .iter()
341 .map(|metadata| metadata.id)
342 .collect();
343 all_applied.extend(applied);
344
345 let prefix = canonical_applied_prefix(all_applied).expect("valid unordered prefix");
346 assert_eq!(prefix, MIGRATION_METADATA);
347 }
348
349 #[test]
350 fn canonical_applied_prefix_rejects_gaps_and_unknown_migrations() {
351 let without_first = MIGRATION_METADATA
352 .iter()
353 .skip(1)
354 .map(|metadata| metadata.id);
355 assert!(canonical_applied_prefix(without_first).is_none());
356
357 let with_unknown = MIGRATION_METADATA
358 .iter()
359 .map(|metadata| metadata.id)
360 .chain(["m20990101_000001_future"]);
361 assert!(canonical_applied_prefix(with_unknown).is_none());
362 }
363
364 #[test]
365 fn frozen_0_6_0_baseline_is_current_prefix() {
366 let metadata_ids: Vec<_> = migration_ids().collect();
367 assert!(metadata_ids.starts_with(BASELINE_0_6_0_MIGRATIONS));
368 }
369}