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