use super::Version;
use crate::error::CoreError;
use std::collections::{HashMap, VecDeque};
#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct MigrationPlan {
pub from_version: Version,
pub toversion: Version,
pub steps: Vec<MigrationStep>,
pub estimated_effort: u32,
pub risk_level: RiskLevel,
pub rollback_plan: Option<RollbackPlan>,
pub prerequisites: Vec<String>,
pub validation_steps: Vec<String>,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct MigrationStep {
pub id: String,
pub name: String,
pub description: String,
pub step_type: StepType,
pub estimated_effort: u32,
pub priority: StepPriority,
pub dependencies: Vec<String>,
pub automation_script: Option<String>,
pub manual_instructions: Option<String>,
pub validation_criteria: Vec<String>,
pub rollback_instructions: Option<String>,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StepType {
CodeChange,
ConfigurationUpdate,
DatabaseMigration,
DependencyUpdate,
FeatureRemoval,
ApiChange,
DataFormatChange,
Testing,
Documentation,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum StepPriority {
Optional,
Low,
Medium,
High,
Critical,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum RiskLevel {
Low,
Medium,
High,
Critical,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct RollbackPlan {
pub steps: Vec<RollbackStep>,
pub estimated_time: u32,
pub backup_requirements: Vec<String>,
pub recovery_validation: Vec<String>,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct RollbackStep {
pub id: String,
pub description: String,
pub instructions: String,
pub validation: Option<String>,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct MigrationExecution {
pub plan: MigrationPlan,
pub current_step: Option<String>,
pub completed_steps: Vec<String>,
pub failed_steps: Vec<String>,
pub status: ExecutionStatus,
pub start_time: chrono::DateTime<chrono::Utc>,
pub end_time: Option<chrono::DateTime<chrono::Utc>>,
pub executionlog: Vec<LogEntry>,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExecutionStatus {
NotStarted,
InProgress,
Completed,
Failed,
Paused,
RolledBack,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct LogEntry {
pub timestamp: chrono::DateTime<chrono::Utc>,
pub level: LogLevel,
pub step_id: Option<String>,
pub message: String,
pub data: Option<String>,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogLevel {
Debug,
Info,
Warning,
Error,
}
pub struct MigrationManager {
migration_templates: HashMap<(Version, Version), MigrationTemplate>,
active_migrations: HashMap<String, MigrationExecution>,
migration_history: Vec<MigrationExecution>,
}
impl MigrationManager {
pub fn new() -> Self {
Self {
migration_templates: HashMap::new(),
active_migrations: HashMap::new(),
migration_history: Vec::new(),
}
}
pub fn register_version(&mut self, _apiversion: &super::ApiVersion) -> Result<(), CoreError> {
Ok(())
}
pub fn create_migration_plan(
&self,
from_version: &Version,
toversion: &Version,
) -> Result<MigrationPlan, CoreError> {
if let Some(template) = self
.migration_templates
.get(&(from_version.clone(), toversion.clone()))
{
return Ok(template.create_plan(from_version.clone(), toversion.clone()));
}
if let Some(path) = self.find_migration_path(from_version, toversion)? {
self.create_multi_step_plan(&path)
} else {
self.create_default_migration_plan(from_version, toversion)
}
}
pub fn has_migration_path(&self, from_version: &Version, toversion: &Version) -> bool {
if self
.migration_templates
.contains_key(&(from_version.clone(), toversion.clone()))
{
return true;
}
self.find_migration_path(from_version, toversion)
.unwrap_or(None)
.is_some()
}
fn find_migration_path(
&self,
from_version: &Version,
toversion: &Version,
) -> Result<Option<Vec<Version>>, CoreError> {
let mut queue = VecDeque::new();
let mut visited = std::collections::HashSet::new();
let mut parent: HashMap<Version, Version> = HashMap::new();
queue.push_back(from_version.clone());
visited.insert(from_version.clone());
while let Some(current) = queue.pop_front() {
if current == *toversion {
let mut path = Vec::new();
let mut node = current;
while let Some(p) = parent.get(&node) {
path.push(node);
node = p.clone();
}
path.push(from_version.clone());
path.reverse();
return Ok(Some(path));
}
for (from, to) in self.migration_templates.keys() {
if *from == current && !visited.contains(to) {
visited.insert(to.clone());
parent.insert(to.clone(), current.clone());
queue.push_back(to.clone());
}
}
}
Ok(None)
}
fn create_multi_step_plan(&self, path: &[Version]) -> Result<MigrationPlan, CoreError> {
let mut all_steps = Vec::new();
let mut total_effort = 0;
let mut max_risk = RiskLevel::Low;
for window in path.windows(2) {
if let Some(template) = self
.migration_templates
.get(&(window[0].clone(), window[1].clone()))
{
let plan = template.create_plan(window[0].clone(), window[1].clone());
all_steps.extend(plan.steps);
total_effort += plan.estimated_effort;
max_risk = max_risk.max(plan.risk_level);
}
}
Ok(MigrationPlan {
from_version: path.first().expect("Operation failed").clone(),
toversion: path.last().expect("Operation failed").clone(),
steps: all_steps,
estimated_effort: total_effort,
risk_level: max_risk,
rollback_plan: None, prerequisites: Vec::new(),
validation_steps: Vec::new(),
})
}
fn create_default_migration_plan(
&self,
from_version: &Version,
toversion: &Version,
) -> Result<MigrationPlan, CoreError> {
let mut steps = Vec::new();
let risk_level = if toversion.major() > from_version.major() {
RiskLevel::High
} else if toversion.minor() > from_version.minor() {
RiskLevel::Medium
} else {
RiskLevel::Low
};
if toversion.major() > from_version.major() {
steps.push(MigrationStep {
id: "major_version_review".to_string(),
name: "Major Version Review".to_string(),
description: "Review all breaking changes in major version upgrade".to_string(),
step_type: StepType::CodeChange,
estimated_effort: 40,
priority: StepPriority::Critical,
dependencies: Vec::new(),
automation_script: None,
manual_instructions: Some(
"Review changelog and identify all breaking changes".to_string(),
),
validation_criteria: vec![
"All breaking changes identified".to_string(),
"Migration strategy defined".to_string(),
],
rollback_instructions: Some("Revert to previous _version".to_string()),
});
}
if toversion.minor() > from_version.minor() {
steps.push(MigrationStep {
id: "minor_version_update".to_string(),
name: "Minor Version Update".to_string(),
description: "Update to new minor version with backward compatibility".to_string(),
step_type: StepType::DependencyUpdate,
estimated_effort: 8,
priority: StepPriority::Medium,
dependencies: Vec::new(),
automation_script: Some("update_dependencies.sh".to_string()),
manual_instructions: None,
validation_criteria: vec!["All tests pass".to_string()],
rollback_instructions: Some("Revert dependency versions".to_string()),
});
}
steps.push(MigrationStep {
id: "comprehensive_testing".to_string(),
name: "Comprehensive Testing".to_string(),
description: "Run full test suite and integration tests".to_string(),
step_type: StepType::Testing,
estimated_effort: 16,
priority: StepPriority::Critical,
dependencies: steps.iter().map(|s| s.id.clone()).collect(),
automation_script: Some("run_tests.sh".to_string()),
manual_instructions: Some("Verify all functionality works as expected".to_string()),
validation_criteria: vec![
"All unit tests pass".to_string(),
"All integration tests pass".to_string(),
"Performance benchmarks met".to_string(),
],
rollback_instructions: None,
});
let estimated_effort = steps.iter().map(|s| s.estimated_effort).sum();
Ok(MigrationPlan {
from_version: from_version.clone(),
toversion: toversion.clone(),
steps,
estimated_effort,
risk_level,
rollback_plan: Some(self.create_default_rollback_plan()),
prerequisites: vec![
"Create backup of current system".to_string(),
"Ensure rollback plan is tested".to_string(),
],
validation_steps: vec![
"Verify system functionality".to_string(),
"Check performance metrics".to_string(),
"Validate data integrity".to_string(),
],
})
}
fn create_default_rollback_plan(&self) -> RollbackPlan {
RollbackPlan {
steps: vec![RollbackStep {
id: "restore_backup".to_string(),
description: "Restore from backup".to_string(),
instructions: "Restore system from pre-migration backup".to_string(),
validation: Some("Verify system is functioning".to_string()),
}],
estimated_time: 30, backup_requirements: vec![
"Full system backup".to_string(),
"Database backup".to_string(),
"Configuration backup".to_string(),
],
recovery_validation: vec![
"System startup successful".to_string(),
"All services running".to_string(),
"Data integrity verified".to_string(),
],
}
}
pub fn start_migration(
&mut self,
plan: MigrationPlan,
executionid: String,
) -> Result<(), CoreError> {
let execution = MigrationExecution {
plan,
current_step: None,
completed_steps: Vec::new(),
failed_steps: Vec::new(),
status: ExecutionStatus::NotStarted,
start_time: chrono::Utc::now(),
end_time: None,
executionlog: Vec::new(),
};
self.active_migrations.insert(executionid, execution);
Ok(())
}
pub fn id_2(&self, executionid: &str) -> Option<&MigrationExecution> {
self.active_migrations.get(executionid)
}
pub fn cleanup_old_plans(&mut self) -> Result<usize, CoreError> {
let cutoff = chrono::Utc::now() - chrono::Duration::days(30);
let initial_count = self.migration_history.len();
self.migration_history
.retain(|execution| execution.start_time > cutoff);
Ok(initial_count - self.migration_history.len())
}
}
impl Default for MigrationManager {
fn default() -> Self {
Self::new()
}
}
struct MigrationTemplate {
steps: Vec<MigrationStepTemplate>,
base_effort: u32,
risk_level: RiskLevel,
}
impl MigrationTemplate {
fn create_plan(&self, from_version: Version, toversion: Version) -> MigrationPlan {
let steps = self
.steps
.iter()
.map(|template| template.create_step())
.collect();
MigrationPlan {
from_version,
toversion,
steps,
estimated_effort: self.base_effort,
risk_level: self.risk_level,
rollback_plan: None,
prerequisites: Vec::new(),
validation_steps: Vec::new(),
}
}
}
struct MigrationStepTemplate {
id: String,
name: String,
description: String,
step_type: StepType,
estimated_effort: u32,
priority: StepPriority,
}
impl MigrationStepTemplate {
fn create_step(&self) -> MigrationStep {
MigrationStep {
id: self.id.clone(),
name: self.name.clone(),
description: self.description.clone(),
step_type: self.step_type,
estimated_effort: self.estimated_effort,
priority: self.priority,
dependencies: Vec::new(),
automation_script: None,
manual_instructions: None,
validation_criteria: Vec::new(),
rollback_instructions: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_migration_manager_creation() {
let manager = MigrationManager::new();
assert!(manager.migration_templates.is_empty());
assert!(manager.active_migrations.is_empty());
}
#[test]
fn test_default_migration_plan() {
let manager = MigrationManager::new();
let from_version = Version::new(1, 0, 0);
let toversion = Version::new(2, 0, 0);
let plan = manager
.create_migration_plan(&from_version, &toversion)
.expect("Operation failed");
assert_eq!(plan.from_version, from_version);
assert_eq!(plan.toversion, toversion);
assert!(!plan.steps.is_empty());
assert_eq!(plan.risk_level, RiskLevel::High); }
#[test]
fn test_minor_version_migration() {
let manager = MigrationManager::new();
let from_version = Version::new(1, 0, 0);
let toversion = Version::new(1, 1, 0);
let plan = manager
.create_migration_plan(&from_version, &toversion)
.expect("Operation failed");
assert_eq!(plan.risk_level, RiskLevel::Medium); }
#[test]
fn test_patch_version_migration() {
let manager = MigrationManager::new();
let from_version = Version::new(1, 0, 0);
let toversion = Version::new(1, 0, 1);
let plan = manager
.create_migration_plan(&from_version, &toversion)
.expect("Operation failed");
assert_eq!(plan.risk_level, RiskLevel::Low); }
#[test]
fn test_migration_execution() {
let mut manager = MigrationManager::new();
let plan = MigrationPlan {
from_version: Version::new(1, 0, 0),
toversion: Version::new(1, 1, 0),
steps: Vec::new(),
estimated_effort: 8,
risk_level: RiskLevel::Medium,
rollback_plan: None,
prerequisites: Vec::new(),
validation_steps: Vec::new(),
};
let executionid = "test_migration_123".to_string();
manager
.start_migration(plan, executionid.clone())
.expect("Operation failed");
}
#[test]
fn test_step_priority_ordering() {
assert!(StepPriority::Critical > StepPriority::High);
assert!(StepPriority::High > StepPriority::Medium);
assert!(StepPriority::Medium > StepPriority::Low);
assert!(StepPriority::Low > StepPriority::Optional);
}
#[test]
fn test_risk_level_ordering() {
assert!(RiskLevel::Critical > RiskLevel::High);
assert!(RiskLevel::High > RiskLevel::Medium);
assert!(RiskLevel::Medium > RiskLevel::Low);
}
}