pub mod artifact;
use std::path::Path;
pub use crate::storage::generation::migrate::{
MigrationConfig, MigrationError, MigrationReport, is_legacy_full_copy_layout,
is_migrated_store, migrate_legacy_store,
};
pub use artifact::{
ArtifactError, ArtifactOutcome, RebuildReason, extract_blob_payload, validate_blob,
validate_generation_manifest, validate_manifest, validate_manifest_fingerprints,
};
pub const CURRENT_FORMAT_VERSION: u32 = 1;
pub const LEGACY_FORMAT_VERSION: u32 = 0;
pub trait MigrationStep: Send + Sync {
fn source_version(&self) -> u32;
fn target_version(&self) -> u32;
fn name(&self) -> &str;
fn migrate(
&self,
storage_root: &Path,
config: &MigrationConfig,
) -> Result<MigrationReport, MigrationError>;
}
pub struct LegacyToCasStep;
impl MigrationStep for LegacyToCasStep {
fn source_version(&self) -> u32 {
LEGACY_FORMAT_VERSION
}
fn target_version(&self) -> u32 {
CURRENT_FORMAT_VERSION
}
fn name(&self) -> &str {
"legacy-full-copy → CAS-LIDX-GEN1"
}
fn migrate(
&self,
storage_root: &Path,
config: &MigrationConfig,
) -> Result<MigrationReport, MigrationError> {
migrate_legacy_store(storage_root, config)
}
}
pub fn migration_steps() -> Vec<Box<dyn MigrationStep>> {
vec![Box::new(LegacyToCasStep)]
}
pub fn detect_format_version(storage_root: &Path) -> u32 {
if is_migrated_store(storage_root) {
CURRENT_FORMAT_VERSION
} else if is_legacy_full_copy_layout(storage_root) {
LEGACY_FORMAT_VERSION
} else {
LEGACY_FORMAT_VERSION
}
}
pub fn migrate_to_current(
storage_root: &Path,
config: &MigrationConfig,
) -> Result<MigrationReport, MigrationError> {
let detected = detect_format_version(storage_root);
if detected == CURRENT_FORMAT_VERSION {
if is_migrated_store(storage_root) {
tracing::debug!(
"migrate_to_current: store already at v{}, no migration needed",
CURRENT_FORMAT_VERSION
);
return migrate_legacy_store(storage_root, config);
}
return Ok(MigrationReport {
detected_legacy: false,
migrated: false,
current_generation: crate::storage::generation::lease::read_current_generation(
storage_root,
),
previous_generation: None,
generations_converted: 0,
generations_deleted: 0,
cas_reclaimed_bytes: 0,
cas_blob_count: 0,
cas_bytes: 0,
jobs_completed_deleted: 0,
jobs_byte_capped: 0,
job_bytes_reclaimed: 0,
job_bytes_remaining: 0,
artifact_bytes_reclaimed: 0,
total_bytes_before: 0,
total_bytes_after: 0,
blob_hashes: Vec::new(),
warnings: Vec::new(),
});
}
for step in migration_steps() {
if detected >= step.source_version() && detected < step.target_version() {
tracing::info!(
"migrate_to_current: running migration step '{}' (v{} → v{})",
step.name(),
step.source_version(),
step.target_version()
);
return step.migrate(storage_root, config);
}
}
tracing::debug!(
"migrate_to_current: no migration step for detected version {}",
detected
);
Ok(MigrationReport {
detected_legacy: false,
migrated: false,
current_generation: crate::storage::generation::lease::read_current_generation(
storage_root,
),
previous_generation: None,
generations_converted: 0,
generations_deleted: 0,
cas_reclaimed_bytes: 0,
cas_blob_count: 0,
cas_bytes: 0,
jobs_completed_deleted: 0,
jobs_byte_capped: 0,
job_bytes_reclaimed: 0,
job_bytes_remaining: 0,
artifact_bytes_reclaimed: 0,
total_bytes_before: 0,
total_bytes_after: 0,
blob_hashes: Vec::new(),
warnings: Vec::new(),
})
}
pub fn needs_migration(storage_root: &Path) -> bool {
is_legacy_full_copy_layout(storage_root)
}
#[cfg(test)]
#[path = "migration_test.rs"]
mod tests;