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