Skip to main content

trustformers_core/versioning/
mod.rs

1//! Model Versioning System for TrustformeRS
2//!
3//! This module provides comprehensive model versioning capabilities including:
4//! - Model version management with metadata
5//! - Artifact storage and retrieval
6//! - Version lifecycle management
7//! - Rollback and promotion strategies
8//! - Integration with A/B testing
9
10mod deployment;
11mod integration;
12mod lifecycle;
13mod metadata;
14mod registry;
15mod storage;
16
17pub use deployment::{
18    ActiveDeployment, DeploymentConfig, DeploymentEvent, DeploymentEventType, DeploymentManager,
19    DeploymentStatistics, DeploymentStatus, DeploymentStrategy, Environment, HealthStatus,
20};
21pub use integration::{
22    ModelRoutingResult, PromotionResult, VersionExperimentConfig, VersionExperimentResult,
23    VersionMetricType, VersionedABTestManager, VersionedExperiment, VersionedExperimentStatus,
24};
25pub use lifecycle::{
26    LifecycleEvent, LifecyclePolicies, LifecycleStatistics, VersionLifecycle, VersionStatus,
27    VersionTransition,
28};
29pub use metadata::{ModelMetadata, ModelSource, ModelTag, VersionedModel};
30pub use registry::{
31    DateRange, ModelRegistry, RegistryStatistics, SortBy, SortOrder, TagMatchMode, VersionFilter,
32    VersionQuery,
33};
34pub use storage::{Artifact, ArtifactType, FileSystemStorage, InMemoryStorage, ModelStorage};
35
36use anyhow::Result;
37use std::sync::Arc;
38use uuid::Uuid;
39
40/// Main model versioning manager
41pub struct ModelVersionManager {
42    registry: Arc<ModelRegistry>,
43    storage: Arc<dyn ModelStorage>,
44    deployment_manager: Arc<DeploymentManager>,
45    lifecycle: Arc<VersionLifecycle>,
46}
47
48impl ModelVersionManager {
49    /// Create a new model version manager
50    pub fn new(storage: Arc<dyn ModelStorage>) -> Self {
51        Self {
52            registry: Arc::new(ModelRegistry::new()),
53            storage: storage.clone(),
54            deployment_manager: Arc::new(DeploymentManager::new(storage)),
55            lifecycle: Arc::new(VersionLifecycle::new()),
56        }
57    }
58
59    /// Register a new model version
60    pub async fn register_version(
61        &self,
62        model_name: &str,
63        version: &str,
64        metadata: ModelMetadata,
65        artifacts: Vec<Artifact>,
66    ) -> Result<Uuid> {
67        // Store artifacts
68        let artifact_ids = self.storage.store_artifacts(&artifacts).await?;
69
70        // Create versioned model
71        let versioned_model = VersionedModel::new(
72            model_name.to_string(),
73            version.to_string(),
74            metadata,
75            artifact_ids,
76        );
77
78        // Register in registry
79        let version_id = self.registry.register(versioned_model).await?;
80
81        // Initialize lifecycle
82        self.lifecycle.initialize_version(version_id).await?;
83
84        tracing::info!(
85            "Registered model version: {}:{} ({})",
86            model_name,
87            version,
88            version_id
89        );
90        Ok(version_id)
91    }
92
93    /// Get a specific model version
94    pub async fn get_version(&self, version_id: Uuid) -> Result<Option<VersionedModel>> {
95        self.registry.get_version(version_id).await
96    }
97
98    /// Get model version by name and version
99    pub async fn get_version_by_name(
100        &self,
101        model_name: &str,
102        version: &str,
103    ) -> Result<Option<VersionedModel>> {
104        self.registry.get_version_by_name(model_name, version).await
105    }
106
107    /// List all versions for a model
108    pub async fn list_versions(&self, model_name: &str) -> Result<Vec<VersionedModel>> {
109        self.registry.list_versions(model_name).await
110    }
111
112    /// Query versions with filters
113    pub async fn query_versions(&self, query: VersionQuery) -> Result<Vec<VersionedModel>> {
114        self.registry.query_versions(query).await
115    }
116
117    /// Promote a version to production
118    pub async fn promote_to_production(&self, version_id: Uuid) -> Result<()> {
119        // Check current status
120        let current_status = self.lifecycle.get_status(version_id).await?;
121        if current_status != VersionStatus::Staging {
122            anyhow::bail!("Can only promote versions from staging to production");
123        }
124
125        // Transition to production
126        self.lifecycle.transition(version_id, VersionTransition::Promote).await?;
127
128        // Deploy to production
129        let version = self
130            .registry
131            .get_version(version_id)
132            .await?
133            .ok_or_else(|| anyhow::anyhow!("Version not found"))?;
134
135        self.deployment_manager.deploy_to_production(version_id, &version).await?;
136
137        tracing::info!("Promoted version {} to production", version_id);
138        Ok(())
139    }
140
141    /// Rollback to a previous version
142    pub async fn rollback_to_version(&self, model_name: &str, target_version: &str) -> Result<()> {
143        let version =
144            self.get_version_by_name(model_name, target_version).await?.ok_or_else(|| {
145                anyhow::anyhow!("Version not found: {}:{}", model_name, target_version)
146            })?;
147
148        // Check if target version is deployable
149        let status = self.lifecycle.get_status(version.id()).await?;
150        if status != VersionStatus::Production && status != VersionStatus::Staging {
151            anyhow::bail!("Can only rollback to production or staging versions");
152        }
153
154        // Perform rollback
155        self.deployment_manager.rollback(model_name, version.id()).await?;
156
157        tracing::info!("Rolled back {} to version {}", model_name, target_version);
158        Ok(())
159    }
160
161    /// Archive old versions
162    pub async fn archive_version(&self, version_id: Uuid) -> Result<()> {
163        // Check if version can be archived
164        let status = self.lifecycle.get_status(version_id).await?;
165        if status == VersionStatus::Production {
166            anyhow::bail!("Cannot archive production version");
167        }
168
169        // Transition to archived
170        self.lifecycle.transition(version_id, VersionTransition::Archive).await?;
171
172        // Move artifacts to archive storage
173        self.storage.archive_version(version_id).await?;
174
175        tracing::info!("Archived version {}", version_id);
176        Ok(())
177    }
178
179    /// Delete a version (permanent)
180    pub async fn delete_version(&self, version_id: Uuid) -> Result<()> {
181        // Check if version can be deleted
182        let status = self.lifecycle.get_status(version_id).await?;
183        if status == VersionStatus::Production {
184            anyhow::bail!("Cannot delete production version");
185        }
186
187        // Remove from storage
188        self.storage.delete_version(version_id).await?;
189
190        // Remove from registry
191        self.registry.remove_version(version_id).await?;
192
193        // Clean up lifecycle
194        self.lifecycle.cleanup_version(version_id).await?;
195
196        tracing::info!("Deleted version {}", version_id);
197        Ok(())
198    }
199
200    /// Get version statistics
201    pub async fn get_version_stats(&self, model_name: &str) -> Result<VersionStats> {
202        let versions = self.list_versions(model_name).await?;
203
204        let mut stats = VersionStats {
205            model_name: model_name.to_string(),
206            total_versions: versions.len(),
207            production_versions: 0,
208            staging_versions: 0,
209            development_versions: 0,
210            archived_versions: 0,
211            latest_version: None,
212            oldest_version: None,
213        };
214
215        if !versions.is_empty() {
216            // Find latest and oldest
217            stats.latest_version = versions
218                .iter()
219                .max_by_key(|v| v.metadata().created_at)
220                .map(|v| v.version().to_string());
221
222            stats.oldest_version = versions
223                .iter()
224                .min_by_key(|v| v.metadata().created_at)
225                .map(|v| v.version().to_string());
226
227            // Count by status
228            for version in &versions {
229                let status = self.lifecycle.get_status(version.id()).await?;
230                match status {
231                    VersionStatus::Production => stats.production_versions += 1,
232                    VersionStatus::Staging => stats.staging_versions += 1,
233                    VersionStatus::Development => stats.development_versions += 1,
234                    VersionStatus::Archived => stats.archived_versions += 1,
235                    _ => {},
236                }
237            }
238        }
239
240        Ok(stats)
241    }
242
243    /// Get registry reference
244    pub fn registry(&self) -> Arc<ModelRegistry> {
245        self.registry.clone()
246    }
247
248    /// Get storage reference
249    pub fn storage(&self) -> Arc<dyn ModelStorage> {
250        self.storage.clone()
251    }
252
253    /// Get deployment manager reference
254    pub fn deployment_manager(&self) -> Arc<DeploymentManager> {
255        self.deployment_manager.clone()
256    }
257
258    /// Get lifecycle manager reference
259    pub fn lifecycle(&self) -> Arc<VersionLifecycle> {
260        self.lifecycle.clone()
261    }
262}
263
264/// Version statistics for a model
265#[derive(Debug, Clone)]
266pub struct VersionStats {
267    pub model_name: String,
268    pub total_versions: usize,
269    pub production_versions: usize,
270    pub staging_versions: usize,
271    pub development_versions: usize,
272    pub archived_versions: usize,
273    pub latest_version: Option<String>,
274    pub oldest_version: Option<String>,
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280    use std::path::PathBuf;
281
282    // Mock storage for testing
283    struct MockStorage;
284
285    #[async_trait::async_trait]
286    impl ModelStorage for MockStorage {
287        async fn store_artifacts(&self, _artifacts: &[Artifact]) -> Result<Vec<Uuid>> {
288            Ok(vec![Uuid::new_v4()])
289        }
290
291        async fn get_artifact(&self, _artifact_id: Uuid) -> Result<Option<Artifact>> {
292            Ok(None)
293        }
294
295        async fn delete_artifacts(&self, _artifact_ids: &[Uuid]) -> Result<()> {
296            Ok(())
297        }
298
299        async fn archive_version(&self, _version_id: Uuid) -> Result<()> {
300            Ok(())
301        }
302
303        async fn delete_version(&self, _version_id: Uuid) -> Result<()> {
304            Ok(())
305        }
306
307        async fn list_artifacts(&self, _version_id: Uuid) -> Result<Vec<Artifact>> {
308            Ok(vec![])
309        }
310    }
311
312    #[tokio::test]
313    async fn test_version_registration() {
314        let storage = Arc::new(MockStorage);
315        let manager = ModelVersionManager::new(storage);
316
317        let metadata = ModelMetadata::builder()
318            .description("Test model".to_string())
319            .created_by("test_user".to_string())
320            .model_type("transformer".to_string())
321            .build();
322
323        let artifacts = vec![Artifact::new(
324            ArtifactType::Model,
325            PathBuf::from("model.bin"),
326            vec![1, 2, 3],
327        )];
328
329        let version_id = manager
330            .register_version("test_model", "1.0.0", metadata, artifacts)
331            .await
332            .expect("operation failed in test");
333        assert!(!version_id.is_nil());
334
335        let retrieved = manager.get_version(version_id).await.expect("async operation failed");
336        assert!(retrieved.is_some());
337        assert_eq!(
338            retrieved.expect("operation failed in test").version(),
339            "1.0.0"
340        );
341    }
342
343    #[tokio::test]
344    async fn test_version_lifecycle() {
345        let storage = Arc::new(MockStorage);
346        let manager = ModelVersionManager::new(storage);
347
348        let metadata = ModelMetadata::builder()
349            .description("Test model".to_string())
350            .created_by("test_user".to_string())
351            .model_type("transformer".to_string())
352            .build();
353
354        let version_id = manager
355            .register_version("test_model", "1.0.0", metadata, vec![])
356            .await
357            .expect("async operation failed");
358
359        // Should start in development
360        let status =
361            manager.lifecycle.get_status(version_id).await.expect("async operation failed");
362        assert_eq!(status, VersionStatus::Development);
363
364        // Move to staging
365        manager
366            .lifecycle
367            .transition(version_id, VersionTransition::ToStaging)
368            .await
369            .expect("operation failed in test");
370        let status =
371            manager.lifecycle.get_status(version_id).await.expect("async operation failed");
372        assert_eq!(status, VersionStatus::Staging);
373    }
374}