scirs2_core/versioning/
migration.rs

1//! # Migration Management
2//!
3//! Comprehensive migration assistance system for API upgrades
4//! and version transitions in production environments.
5
6use super::Version;
7use crate::error::CoreError;
8use std::collections::{HashMap, VecDeque};
9
10use serde::{Deserialize, Serialize};
11
12/// Migration plan for upgrading between versions
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct MigrationPlan {
15    /// Source version
16    pub from_version: Version,
17    /// Target version
18    pub toversion: Version,
19    /// Ordered migration steps
20    pub steps: Vec<MigrationStep>,
21    /// Estimated total effort in hours
22    pub estimated_effort: u32,
23    /// Risk level of the migration
24    pub risk_level: RiskLevel,
25    /// Required rollback plan
26    pub rollback_plan: Option<RollbackPlan>,
27    /// Prerequisites before migration
28    pub prerequisites: Vec<String>,
29    /// Post-migration validation steps
30    pub validation_steps: Vec<String>,
31}
32
33/// Individual migration step
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct MigrationStep {
36    /// Step identifier
37    pub id: String,
38    /// Step name
39    pub name: String,
40    /// Step description
41    pub description: String,
42    /// Step type
43    pub step_type: StepType,
44    /// Estimated effort in hours
45    pub estimated_effort: u32,
46    /// Step priority
47    pub priority: StepPriority,
48    /// Dependencies on other steps
49    pub dependencies: Vec<String>,
50    /// Automation script (if available)
51    pub automation_script: Option<String>,
52    /// Manual instructions
53    pub manual_instructions: Option<String>,
54    /// Validation criteria
55    pub validation_criteria: Vec<String>,
56    /// Rollback instructions
57    pub rollback_instructions: Option<String>,
58}
59
60/// Types of migration steps
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
62pub enum StepType {
63    /// Code changes required
64    CodeChange,
65    /// Configuration update
66    ConfigurationUpdate,
67    /// Database migration
68    DatabaseMigration,
69    /// Dependency update
70    DependencyUpdate,
71    /// Feature removal
72    FeatureRemoval,
73    /// API endpoint change
74    ApiChange,
75    /// Data format change
76    DataFormatChange,
77    /// Testing and validation
78    Testing,
79    /// Documentation update
80    Documentation,
81}
82
83/// Step priority levels
84#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
85pub enum StepPriority {
86    /// Optional step
87    Optional,
88    /// Low priority
89    Low,
90    /// Medium priority
91    Medium,
92    /// High priority
93    High,
94    /// Critical - must be completed
95    Critical,
96}
97
98/// Risk levels for migrations
99#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
100pub enum RiskLevel {
101    /// Low risk migration
102    Low,
103    /// Medium risk migration
104    Medium,
105    /// High risk migration
106    High,
107    /// Critical risk migration
108    Critical,
109}
110
111/// Rollback plan for migration failures
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct RollbackPlan {
114    /// Rollback steps in order
115    pub steps: Vec<RollbackStep>,
116    /// Estimated rollback time
117    pub estimated_time: u32,
118    /// Data backup requirements
119    pub backup_requirements: Vec<String>,
120    /// Recovery validation steps
121    pub recovery_validation: Vec<String>,
122}
123
124/// Individual rollback step
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct RollbackStep {
127    /// Step identifier
128    pub id: String,
129    /// Step description
130    pub description: String,
131    /// Rollback commands or instructions
132    pub instructions: String,
133    /// Validation after rollback
134    pub validation: Option<String>,
135}
136
137/// Migration execution status
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct MigrationExecution {
140    /// Migration plan being executed
141    pub plan: MigrationPlan,
142    /// Current step being executed
143    pub current_step: Option<String>,
144    /// Completed steps
145    pub completed_steps: Vec<String>,
146    /// Failed steps
147    pub failed_steps: Vec<String>,
148    /// Execution status
149    pub status: ExecutionStatus,
150    /// Start time
151    pub start_time: chrono::DateTime<chrono::Utc>,
152    /// End time (if completed)
153    pub end_time: Option<chrono::DateTime<chrono::Utc>>,
154    /// Execution log
155    pub executionlog: Vec<LogEntry>,
156}
157
158/// Execution status
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
160pub enum ExecutionStatus {
161    /// Migration not started
162    NotStarted,
163    /// Migration in progress
164    InProgress,
165    /// Migration completed successfully
166    Completed,
167    /// Migration failed
168    Failed,
169    /// Migration paused
170    Paused,
171    /// Migration rolled back
172    RolledBack,
173}
174
175/// Log entry for migration execution
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct LogEntry {
178    /// Timestamp
179    pub timestamp: chrono::DateTime<chrono::Utc>,
180    /// Log level
181    pub level: LogLevel,
182    /// Step ID (if applicable)
183    pub step_id: Option<String>,
184    /// Log message
185    pub message: String,
186    /// Additional data
187    pub data: Option<String>,
188}
189
190/// Log levels
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
192pub enum LogLevel {
193    /// Debug information
194    Debug,
195    /// Informational message
196    Info,
197    /// Warning message
198    Warning,
199    /// Error message
200    Error,
201}
202
203/// Migration manager implementation
204pub struct MigrationManager {
205    /// Migration templates by version transitions
206    migration_templates: HashMap<(Version, Version), MigrationTemplate>,
207    /// Active migrations
208    active_migrations: HashMap<String, MigrationExecution>,
209    /// Migration history
210    migration_history: Vec<MigrationExecution>,
211}
212
213impl MigrationManager {
214    /// Create a new migration manager
215    pub fn new() -> Self {
216        Self {
217            migration_templates: HashMap::new(),
218            active_migrations: HashMap::new(),
219            migration_history: Vec::new(),
220        }
221    }
222
223    /// Register a version for migration planning
224    pub fn register_version(&mut self, _apiversion: &super::ApiVersion) -> Result<(), CoreError> {
225        // This would register _version-specific migration information
226        Ok(())
227    }
228
229    /// Create a migration plan between versions
230    pub fn create_migration_plan(
231        &self,
232        from_version: &Version,
233        toversion: &Version,
234    ) -> Result<MigrationPlan, CoreError> {
235        // Check if direct migration template exists
236        if let Some(template) = self
237            .migration_templates
238            .get(&(from_version.clone(), toversion.clone()))
239        {
240            return Ok(template.create_plan(from_version.clone(), toversion.clone()));
241        }
242
243        // Try to find a path through intermediate versions
244        if let Some(path) = self.find_migration_path(from_version, toversion)? {
245            self.create_multi_step_plan(&path)
246        } else {
247            self.create_default_migration_plan(from_version, toversion)
248        }
249    }
250
251    /// Check if a migration path exists
252    pub fn has_migration_path(&self, from_version: &Version, toversion: &Version) -> bool {
253        // Check direct path
254        if self
255            .migration_templates
256            .contains_key(&(from_version.clone(), toversion.clone()))
257        {
258            return true;
259        }
260
261        // Check if we can find an indirect path
262        self.find_migration_path(from_version, toversion)
263            .unwrap_or(None)
264            .is_some()
265    }
266
267    /// Find migration path through intermediate versions
268    fn find_migration_path(
269        &self,
270        from_version: &Version,
271        toversion: &Version,
272    ) -> Result<Option<Vec<Version>>, CoreError> {
273        // BFS to find shortest path
274        let mut queue = VecDeque::new();
275        let mut visited = std::collections::HashSet::new();
276        let mut parent: HashMap<Version, Version> = HashMap::new();
277
278        queue.push_back(from_version.clone());
279        visited.insert(from_version.clone());
280
281        while let Some(current) = queue.pop_front() {
282            if current == *toversion {
283                // Reconstruct path
284                let mut path = Vec::new();
285                let mut node = current;
286
287                while let Some(p) = parent.get(&node) {
288                    path.push(node);
289                    node = p.clone();
290                }
291                path.push(from_version.clone());
292                path.reverse();
293
294                return Ok(Some(path));
295            }
296
297            // Find all versions reachable from current
298            for (from, to) in self.migration_templates.keys() {
299                if *from == current && !visited.contains(to) {
300                    visited.insert(to.clone());
301                    parent.insert(to.clone(), current.clone());
302                    queue.push_back(to.clone());
303                }
304            }
305        }
306
307        Ok(None)
308    }
309
310    /// Create multi-step migration plan
311    fn create_multi_step_plan(&self, path: &[Version]) -> Result<MigrationPlan, CoreError> {
312        let mut all_steps = Vec::new();
313        let mut total_effort = 0;
314        let mut max_risk = RiskLevel::Low;
315
316        for window in path.windows(2) {
317            if let Some(template) = self
318                .migration_templates
319                .get(&(window[0].clone(), window[1].clone()))
320            {
321                let plan = template.create_plan(window[0].clone(), window[1].clone());
322                all_steps.extend(plan.steps);
323                total_effort += plan.estimated_effort;
324                max_risk = max_risk.max(plan.risk_level);
325            }
326        }
327
328        Ok(MigrationPlan {
329            from_version: path.first().unwrap().clone(),
330            toversion: path.last().unwrap().clone(),
331            steps: all_steps,
332            estimated_effort: total_effort,
333            risk_level: max_risk,
334            rollback_plan: None, // Would be constructed from individual rollback plans
335            prerequisites: Vec::new(),
336            validation_steps: Vec::new(),
337        })
338    }
339
340    /// Create default migration plan when no template exists
341    fn create_default_migration_plan(
342        &self,
343        from_version: &Version,
344        toversion: &Version,
345    ) -> Result<MigrationPlan, CoreError> {
346        let mut steps = Vec::new();
347        let risk_level = if toversion.major() > from_version.major() {
348            RiskLevel::High
349        } else if toversion.minor() > from_version.minor() {
350            RiskLevel::Medium
351        } else {
352            RiskLevel::Low
353        };
354
355        // Add default steps based on version difference
356        if toversion.major() > from_version.major() {
357            steps.push(MigrationStep {
358                id: "major_version_review".to_string(),
359                name: "Major Version Review".to_string(),
360                description: "Review all breaking changes in major version upgrade".to_string(),
361                step_type: StepType::CodeChange,
362                estimated_effort: 40,
363                priority: StepPriority::Critical,
364                dependencies: Vec::new(),
365                automation_script: None,
366                manual_instructions: Some(
367                    "Review changelog and identify all breaking changes".to_string(),
368                ),
369                validation_criteria: vec![
370                    "All breaking changes identified".to_string(),
371                    "Migration strategy defined".to_string(),
372                ],
373                rollback_instructions: Some("Revert to previous _version".to_string()),
374            });
375        }
376
377        if toversion.minor() > from_version.minor() {
378            steps.push(MigrationStep {
379                id: "minor_version_update".to_string(),
380                name: "Minor Version Update".to_string(),
381                description: "Update to new minor version with backward compatibility".to_string(),
382                step_type: StepType::DependencyUpdate,
383                estimated_effort: 8,
384                priority: StepPriority::Medium,
385                dependencies: Vec::new(),
386                automation_script: Some("update_dependencies.sh".to_string()),
387                manual_instructions: None,
388                validation_criteria: vec!["All tests pass".to_string()],
389                rollback_instructions: Some("Revert dependency versions".to_string()),
390            });
391        }
392
393        // Always add testing step
394        steps.push(MigrationStep {
395            id: "comprehensive_testing".to_string(),
396            name: "Comprehensive Testing".to_string(),
397            description: "Run full test suite and integration tests".to_string(),
398            step_type: StepType::Testing,
399            estimated_effort: 16,
400            priority: StepPriority::Critical,
401            dependencies: steps.iter().map(|s| s.id.clone()).collect(),
402            automation_script: Some("run_tests.sh".to_string()),
403            manual_instructions: Some("Verify all functionality works as expected".to_string()),
404            validation_criteria: vec![
405                "All unit tests pass".to_string(),
406                "All integration tests pass".to_string(),
407                "Performance benchmarks met".to_string(),
408            ],
409            rollback_instructions: None,
410        });
411
412        let estimated_effort = steps.iter().map(|s| s.estimated_effort).sum();
413
414        Ok(MigrationPlan {
415            from_version: from_version.clone(),
416            toversion: toversion.clone(),
417            steps,
418            estimated_effort,
419            risk_level,
420            rollback_plan: Some(self.create_default_rollback_plan()),
421            prerequisites: vec![
422                "Create backup of current system".to_string(),
423                "Ensure rollback plan is tested".to_string(),
424            ],
425            validation_steps: vec![
426                "Verify system functionality".to_string(),
427                "Check performance metrics".to_string(),
428                "Validate data integrity".to_string(),
429            ],
430        })
431    }
432
433    /// Create default rollback plan
434    fn create_default_rollback_plan(&self) -> RollbackPlan {
435        RollbackPlan {
436            steps: vec![RollbackStep {
437                id: "restore_backup".to_string(),
438                description: "Restore from backup".to_string(),
439                instructions: "Restore system from pre-migration backup".to_string(),
440                validation: Some("Verify system is functioning".to_string()),
441            }],
442            estimated_time: 30, // 30 minutes
443            backup_requirements: vec![
444                "Full system backup".to_string(),
445                "Database backup".to_string(),
446                "Configuration backup".to_string(),
447            ],
448            recovery_validation: vec![
449                "System startup successful".to_string(),
450                "All services running".to_string(),
451                "Data integrity verified".to_string(),
452            ],
453        }
454    }
455
456    /// Start migration execution
457    pub fn start_migration(
458        &mut self,
459        plan: MigrationPlan,
460        executionid: String,
461    ) -> Result<(), CoreError> {
462        let execution = MigrationExecution {
463            plan,
464            current_step: None,
465            completed_steps: Vec::new(),
466            failed_steps: Vec::new(),
467            status: ExecutionStatus::NotStarted,
468            start_time: chrono::Utc::now(),
469            end_time: None,
470            executionlog: Vec::new(),
471        };
472
473        self.active_migrations.insert(executionid, execution);
474        Ok(())
475    }
476
477    /// Get migration execution status
478    pub fn id_2(&self, executionid: &str) -> Option<&MigrationExecution> {
479        self.active_migrations.get(executionid)
480    }
481
482    /// Clean up old migration plans
483    pub fn cleanup_old_plans(&mut self) -> Result<usize, CoreError> {
484        let cutoff = chrono::Utc::now() - chrono::Duration::days(30);
485        let initial_count = self.migration_history.len();
486
487        self.migration_history
488            .retain(|execution| execution.start_time > cutoff);
489
490        Ok(initial_count - self.migration_history.len())
491    }
492}
493
494impl Default for MigrationManager {
495    fn default() -> Self {
496        Self::new()
497    }
498}
499
500/// Migration template for version transitions
501struct MigrationTemplate {
502    /// Template steps
503    steps: Vec<MigrationStepTemplate>,
504    /// Base effort estimate
505    base_effort: u32,
506    /// Risk level
507    risk_level: RiskLevel,
508}
509
510impl MigrationTemplate {
511    fn create_plan(&self, from_version: Version, toversion: Version) -> MigrationPlan {
512        let steps = self
513            .steps
514            .iter()
515            .map(|template| template.create_step())
516            .collect();
517
518        MigrationPlan {
519            from_version,
520            toversion,
521            steps,
522            estimated_effort: self.base_effort,
523            risk_level: self.risk_level,
524            rollback_plan: None,
525            prerequisites: Vec::new(),
526            validation_steps: Vec::new(),
527        }
528    }
529}
530
531/// Template for migration steps
532struct MigrationStepTemplate {
533    id: String,
534    name: String,
535    description: String,
536    step_type: StepType,
537    estimated_effort: u32,
538    priority: StepPriority,
539}
540
541impl MigrationStepTemplate {
542    fn create_step(&self) -> MigrationStep {
543        MigrationStep {
544            id: self.id.clone(),
545            name: self.name.clone(),
546            description: self.description.clone(),
547            step_type: self.step_type,
548            estimated_effort: self.estimated_effort,
549            priority: self.priority,
550            dependencies: Vec::new(),
551            automation_script: None,
552            manual_instructions: None,
553            validation_criteria: Vec::new(),
554            rollback_instructions: None,
555        }
556    }
557}
558
559#[cfg(test)]
560mod tests {
561    use super::*;
562
563    #[test]
564    fn test_migration_manager_creation() {
565        let manager = MigrationManager::new();
566        assert!(manager.migration_templates.is_empty());
567        assert!(manager.active_migrations.is_empty());
568    }
569
570    #[test]
571    fn test_default_migration_plan() {
572        let manager = MigrationManager::new();
573        let from_version = Version::new(1, 0, 0);
574        let toversion = Version::new(2, 0, 0);
575
576        let plan = manager
577            .create_migration_plan(&from_version, &toversion)
578            .unwrap();
579        assert_eq!(plan.from_version, from_version);
580        assert_eq!(plan.toversion, toversion);
581        assert!(!plan.steps.is_empty());
582        assert_eq!(plan.risk_level, RiskLevel::High); // Major version change
583    }
584
585    #[test]
586    fn test_minor_version_migration() {
587        let manager = MigrationManager::new();
588        let from_version = Version::new(1, 0, 0);
589        let toversion = Version::new(1, 1, 0);
590
591        let plan = manager
592            .create_migration_plan(&from_version, &toversion)
593            .unwrap();
594        assert_eq!(plan.risk_level, RiskLevel::Medium); // Minor version change
595    }
596
597    #[test]
598    fn test_patch_version_migration() {
599        let manager = MigrationManager::new();
600        let from_version = Version::new(1, 0, 0);
601        let toversion = Version::new(1, 0, 1);
602
603        let plan = manager
604            .create_migration_plan(&from_version, &toversion)
605            .unwrap();
606        assert_eq!(plan.risk_level, RiskLevel::Low); // Patch version change
607    }
608
609    #[test]
610    fn test_migration_execution() {
611        let mut manager = MigrationManager::new();
612        let plan = MigrationPlan {
613            from_version: Version::new(1, 0, 0),
614            toversion: Version::new(1, 1, 0),
615            steps: Vec::new(),
616            estimated_effort: 8,
617            risk_level: RiskLevel::Medium,
618            rollback_plan: None,
619            prerequisites: Vec::new(),
620            validation_steps: Vec::new(),
621        };
622
623        let executionid = "test_migration_123".to_string();
624        manager.start_migration(plan, executionid.clone()).unwrap();
625
626        // let status = manager.get_migration_status(&executionid);
627        // assert!(status.is_some());
628        // assert_eq!(status.unwrap().status, ExecutionStatus::NotStarted);
629    }
630
631    #[test]
632    fn test_step_priority_ordering() {
633        assert!(StepPriority::Critical > StepPriority::High);
634        assert!(StepPriority::High > StepPriority::Medium);
635        assert!(StepPriority::Medium > StepPriority::Low);
636        assert!(StepPriority::Low > StepPriority::Optional);
637    }
638
639    #[test]
640    fn test_risk_level_ordering() {
641        assert!(RiskLevel::Critical > RiskLevel::High);
642        assert!(RiskLevel::High > RiskLevel::Medium);
643        assert!(RiskLevel::Medium > RiskLevel::Low);
644    }
645}