Skip to main content

client_core/
upgrade_strategy.rs

1use std::fmt::Display;
2use std::path::PathBuf;
3
4use crate::{
5    api_types::{EnhancedServiceManifest, PatchPackageInfo},
6    architecture::Architecture,
7    constants::docker::get_compose_file_path,
8    constants::docker::get_docker_work_dir,
9    version::Version,
10};
11use anyhow::Result;
12use tracing::{debug, info};
13
14#[derive(Debug, Clone, PartialEq)]
15pub enum DownloadType {
16    /// 全量升级
17    Full,
18    /// 增量升级
19    Patch,
20}
21
22impl Display for DownloadType {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        match self {
25            DownloadType::Full => write!(f, "full"),
26            DownloadType::Patch => write!(f, "patch"),
27        }
28    }
29}
30
31/// 升级策略类型
32#[derive(Debug, Clone, PartialEq)]
33pub enum UpgradeStrategy {
34    /// 全量升级
35    FullUpgrade {
36        /// 下载URL
37        url: String,
38        /// 文件哈希
39        hash: String,
40        /// 签名
41        signature: String,
42        /// 目标版本
43        target_version: Version,
44        /// 下载类型
45        download_type: DownloadType,
46    },
47    /// 增量升级(补丁)
48    PatchUpgrade {
49        /// 补丁包信息
50        patch_info: PatchPackageInfo,
51        /// 目标版本
52        target_version: Version,
53        /// 下载类型
54        download_type: DownloadType,
55    },
56    /// 无需升级
57    NoUpgrade {
58        /// 目标版本
59        target_version: Version,
60    },
61}
62
63impl UpgradeStrategy {
64    ///获取此次升级,变更的文件,或者目录,使用相对工作目录的路径,工作目录是:./docker ,如果是全量升级,只备份: ./data 目录; 增量升级,还需要额外备份增量升级变更的文件或者目录
65    pub fn get_changed_files(&self) -> Vec<PathBuf> {
66        let change_files = match self {
67            UpgradeStrategy::FullUpgrade { .. } => vec!["data".to_string(), "upload".to_string()],
68            UpgradeStrategy::PatchUpgrade { patch_info, .. } => patch_info.get_changed_files(),
69            UpgradeStrategy::NoUpgrade { .. } => {
70                vec![]
71            }
72        };
73        change_files.into_iter().map(PathBuf::from).collect()
74    }
75}
76
77/// 决策因素分析
78#[derive(Debug, Clone)]
79pub struct DecisionFactors {
80    /// 版本兼容性分数(0.0-1.0)
81    pub version_compatibility: f64,
82    /// 网络条件分数(0.0-1.0)
83    pub network_condition: f64,
84    /// 磁盘空间分数(0.0-1.0)
85    pub disk_space: f64,
86    /// 风险评估分数(0.0-1.0,越低越好)
87    pub risk_assessment: f64,
88    /// 时间效率分数(0.0-1.0)
89    pub time_efficiency: f64,
90}
91
92/// 升级策略管理器
93#[derive(Debug)]
94pub struct UpgradeStrategyManager {
95    ///server端docker应用升级版本信息
96    manifest: EnhancedServiceManifest,
97    ///当前客户端版本
98    current_version: String,
99    ///是否强制全量升级
100    force_full: bool,
101    ///当前客户端架构
102    architecture: Architecture,
103}
104
105impl UpgradeStrategyManager {
106    /// 创建新的升级策略管理器
107    pub fn new(
108        current_version: String,
109        force_full: bool,
110        manifest: EnhancedServiceManifest,
111    ) -> Self {
112        Self {
113            manifest,
114            current_version,
115            force_full,
116            architecture: Architecture::detect(),
117        }
118    }
119
120    /// 确定升级策略(简化版本)
121    pub fn determine_strategy(&self) -> Result<UpgradeStrategy> {
122        info!("Starting upgrade strategy decision");
123        info!("   Current version: {}", self.current_version);
124        info!("   Server version: {}", self.manifest.version);
125        info!("   Target architecture: {}", self.architecture.as_str());
126        info!("   Force full upgrade: {}", self.force_full);
127
128        // 1. 解析当前版本
129        let current_ver = self.current_version.parse::<Version>()?;
130
131        // 2. 首先与基础服务器版本比较,确定是否需要升级
132        let server_ver = self.manifest.version.clone();
133        //比较当前版本和服务器版本,判断是全量,还是增量升级,还是不需要升级
134        let base_comparison = current_ver.compare_detailed(&server_ver);
135
136        info!("Current version details: {:?}", current_ver);
137        info!("Server version details: {:?}", server_ver);
138        info!("Base version comparison result: {:?}", base_comparison);
139
140        // 3. 强制全量升级
141        if self.force_full {
142            info!("Force executing full upgrade");
143            return self.select_full_upgrade_strategy();
144        }
145        //判断工作目录下,是否有docker目录,如果没有docker目录,则也使用全量升级
146        let work_dir = get_docker_work_dir();
147        let compose_file_path = get_compose_file_path();
148        if !work_dir.exists() || !compose_file_path.exists() {
149            info!("No docker directory or compose file found in working directory, selecting full upgrade strategy");
150            return self.select_full_upgrade_strategy();
151        }
152
153        // 4. 根据版本比较结果决策
154        match base_comparison {
155            crate::version::VersionComparison::Equal | crate::version::VersionComparison::Newer => {
156                info!("Current version is already latest, no upgrade needed");
157                Ok(UpgradeStrategy::NoUpgrade {
158                    target_version: self.manifest.version.clone(),
159                })
160            }
161            crate::version::VersionComparison::PatchUpgradeable => {
162                // 可以进行增量升级
163                if !self.has_patch_for_architecture() {
164                    info!("No incremental upgrade package for current architecture, selecting full upgrade strategy");
165                    self.select_full_upgrade_strategy()
166                } else {
167                    info!("Selecting incremental upgrade strategy");
168                    self.select_patch_upgrade_strategy()
169                }
170            }
171            crate::version::VersionComparison::FullUpgradeRequired => {
172                // 需要全量升级
173                info!("Selecting full upgrade strategy");
174                self.select_full_upgrade_strategy()
175            }
176        }
177    }
178
179    /// 选择全量升级策略
180    pub fn select_full_upgrade_strategy(&self) -> Result<UpgradeStrategy> {
181        debug!("Selecting full upgrade strategy");
182
183        if let Some(_) = &self.manifest.platforms {
184            //使用分架构的全量包
185            let platform_info = self.get_platform_package()?;
186
187            debug!("Using architecture-specific full package: {}", &platform_info.url);
188            Ok(UpgradeStrategy::FullUpgrade {
189                url: platform_info.url.clone(),
190                hash: "external".to_string(), // 平台包通常没有预设哈希
191                signature: platform_info.signature.clone(),
192                target_version: self.manifest.version.clone(),
193                download_type: DownloadType::Full,
194            })
195        } else {
196            if let Some(package_info) = &self.manifest.packages {
197                let full_info = &package_info.full;
198                debug!("Using generic full package: {}", &full_info.url);
199                Ok(UpgradeStrategy::FullUpgrade {
200                    url: full_info.url.clone(),
201                    hash: full_info.hash.clone(),
202                    signature: full_info.signature.clone(),
203                    target_version: self.manifest.version.clone(),
204                    download_type: DownloadType::Full,
205                })
206            } else {
207                //未找到对应架构的全量升级包,这里主动报错
208                Err(anyhow::anyhow!("Full upgrade package for corresponding architecture not found"))
209            }
210        }
211    }
212
213    /// 选择增量升级策略
214    pub fn select_patch_upgrade_strategy(&self) -> Result<UpgradeStrategy> {
215        debug!("Selecting incremental upgrade strategy");
216
217        let patch_info = self.get_patch_package()?;
218
219        debug!("Using architecture-specific patch package: {}", &patch_info.url);
220        Ok(UpgradeStrategy::PatchUpgrade {
221            patch_info: patch_info.clone(),
222            target_version: self.manifest.version.clone(),
223            download_type: DownloadType::Patch,
224        })
225    }
226
227    /// 获取指定架构的平台包信息
228    fn get_platform_package<'a>(&self) -> Result<crate::api_types::PlatformPackageInfo> {
229        if let Some(platforms) = self.manifest.platforms.as_ref() {
230            match self.architecture {
231                Architecture::X86_64 => platforms
232                    .x86_64
233                    .clone()
234                    .ok_or_else(|| anyhow::anyhow!("Full upgrade package for x86_64 architecture not found")),
235                Architecture::Aarch64 => platforms
236                    .aarch64
237                    .clone()
238                    .ok_or_else(|| anyhow::anyhow!("Full upgrade package for aarch64 architecture not found")),
239                Architecture::Unsupported(_) => Err(anyhow::anyhow!("Full upgrade package for this architecture not found")),
240            }
241        } else {
242            //未找到对应架构的全量升级包,这里主动报错
243            Err(anyhow::anyhow!(
244                "Full upgrade package for the current architecture not found"
245            ))
246        }
247    }
248
249    /// 获取指定架构的补丁包信息
250    fn get_patch_package(&self) -> Result<&PatchPackageInfo> {
251        let patch_info = self
252            .manifest
253            .patch
254            .as_ref()
255            .ok_or_else(|| anyhow::anyhow!("Server does not support incremental upgrade"))?;
256        match self.architecture {
257            Architecture::X86_64 => patch_info
258                .x86_64
259                .as_ref()
260                .ok_or_else(|| anyhow::anyhow!("Patch package for x86_64 architecture not available")),
261            Architecture::Aarch64 => patch_info
262                .aarch64
263                .as_ref()
264                .ok_or_else(|| anyhow::anyhow!("Patch package for aarch64 architecture not available")),
265            Architecture::Unsupported(_) => Err(anyhow::anyhow!("Unsupported architecture")),
266        }
267    }
268
269    /// 检查指定架构是否有可用的补丁
270    fn has_patch_for_architecture(&self) -> bool {
271        self.manifest
272            .patch
273            .as_ref()
274            .map(|patch| match self.architecture {
275                Architecture::X86_64 => patch.x86_64.is_some(),
276                Architecture::Aarch64 => patch.aarch64.is_some(),
277                Architecture::Unsupported(_) => false,
278            })
279            .unwrap_or(false)
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286    use crate::api_types::*;
287    use std::fs;
288    use tempfile::TempDir;
289
290    // 创建测试用的增强服务清单
291    fn create_test_manifest() -> EnhancedServiceManifest {
292        EnhancedServiceManifest {
293            version: "0.0.13.2".parse::<Version>().unwrap(),
294            release_date: "2025-01-12T10:00:00Z".to_string(),
295            release_notes: "测试版本".to_string(),
296            packages: Some(ServicePackages {
297                full: PackageInfo {
298                    url: "https://example.com/docker.zip".to_string(),
299                    hash: "sha256:full_hash".to_string(),
300                    signature: "full_signature".to_string(),
301                    size: 100 * 1024 * 1024, // 100MB
302                },
303                patch: None,
304            }),
305            platforms: Some(PlatformPackages {
306                x86_64: Some(PlatformPackageInfo {
307                    signature: "x86_64_signature".to_string(),
308                    url: "https://example.com/x86_64/docker.zip".to_string(),
309                }),
310                aarch64: Some(PlatformPackageInfo {
311                    signature: "aarch64_signature".to_string(),
312                    url: "https://example.com/aarch64/docker.zip".to_string(),
313                }),
314            }),
315            patch: Some(PatchInfo {
316                x86_64: Some(PatchPackageInfo {
317                    url: "https://example.com/patches/x86_64-patch.tar.gz".to_string(),
318                    hash: Some("sha256:patch_hash_x86_64".to_string()),
319                    signature: Some("patch_signature_x86_64".to_string()),
320                    operations: PatchOperations {
321                        replace: Some(ReplaceOperations {
322                            files: vec!["app.jar".to_string(), "config.yml".to_string()],
323                            directories: vec!["front/".to_string()],
324                        }),
325                        delete: Some(ReplaceOperations {
326                            files: vec![
327                                "old-files/app.jar".to_string(),
328                                "old-files/config.yml".to_string(),
329                            ],
330                            directories: vec!["old-files/front/".to_string()],
331                        }),
332                    },
333                    notes: None,
334                }),
335                aarch64: Some(PatchPackageInfo {
336                    url: "https://example.com/patches/aarch64-patch.tar.gz".to_string(),
337                    hash: Some("sha256:patch_hash_aarch64".to_string()),
338                    signature: Some("patch_signature_aarch64".to_string()),
339                    operations: PatchOperations {
340                        replace: Some(ReplaceOperations {
341                            files: vec!["app.jar".to_string(), "config.yml".to_string()],
342                            directories: vec!["front/".to_string()],
343                        }),
344                        delete: Some(ReplaceOperations {
345                            files: vec![
346                                "old-files/app.jar".to_string(),
347                                "old-files/config.yml".to_string(),
348                            ],
349                            directories: vec!["old-files/front/".to_string()],
350                        }),
351                    },
352                    notes: None,
353                }),
354            }),
355        }
356    }
357
358    // 创建测试环境,包括必要的docker目录和文件
359    fn setup_test_environment() -> TempDir {
360        let temp_dir = TempDir::new().unwrap();
361
362        // 创建docker目录
363        let docker_dir = temp_dir.path().join("docker");
364        fs::create_dir(&docker_dir).unwrap();
365
366        // 创建docker-compose.yml文件
367        let compose_file = docker_dir.join("docker-compose.yml");
368        fs::write(
369            &compose_file,
370            "version: '3.8'\nservices:\n  test:\n    image: hello-world",
371        )
372        .unwrap();
373
374        // 设置当前工作目录为临时目录
375        std::env::set_current_dir(&temp_dir).unwrap();
376
377        temp_dir
378    }
379
380    #[test]
381    fn test_no_upgrade_needed() {
382        // 设置测试环境
383        let _temp_dir = setup_test_environment();
384
385        let manager =
386            UpgradeStrategyManager::new("0.0.13.2".to_string(), false, create_test_manifest());
387
388        // 当前版本与服务器版本相同
389        let strategy = manager.determine_strategy().unwrap();
390
391        assert!(matches!(strategy, UpgradeStrategy::NoUpgrade { .. }));
392    }
393
394    #[test]
395    fn test_current_version_newer() {
396        // 设置测试环境
397        let _temp_dir = setup_test_environment();
398
399        let manager =
400            UpgradeStrategyManager::new("0.0.13.4".to_string(), false, create_test_manifest());
401
402        // 当前版本比服务器版本新
403        let strategy = manager.determine_strategy().unwrap();
404
405        assert!(matches!(strategy, UpgradeStrategy::NoUpgrade { .. }));
406    }
407
408    #[test]
409    fn test_full_upgrade_different_base_version() {
410        // 设置测试环境
411        let _temp_dir = setup_test_environment();
412
413        let manager =
414            UpgradeStrategyManager::new("0.0.12".to_string(), false, create_test_manifest());
415
416        // 不同基础版本,需要全量升级
417        let strategy = manager.determine_strategy().unwrap();
418
419        match strategy {
420            UpgradeStrategy::FullUpgrade {
421                url,
422                target_version,
423                ..
424            } => {
425                assert_eq!(url, "https://example.com/aarch64/docker.zip");
426                assert_eq!(target_version, "0.0.13.2".parse::<Version>().unwrap());
427            }
428            _ => panic!("应该选择全量升级策略"),
429        }
430    }
431
432    #[test]
433    fn test_patch_upgrade_same_base_version() {
434        // 设置测试环境
435        let _temp_dir = setup_test_environment();
436
437        let manager =
438            UpgradeStrategyManager::new("0.0.13".to_string(), false, create_test_manifest());
439
440        // 相同基础版本,可以增量升级
441        let strategy = manager.determine_strategy().unwrap();
442
443        match strategy {
444            UpgradeStrategy::PatchUpgrade { target_version, .. } => {
445                assert_eq!(target_version, "0.0.13.2".parse::<Version>().unwrap());
446            }
447            _ => panic!("应该选择增量升级策略"),
448        }
449    }
450
451    #[test]
452    fn test_force_full_upgrade() {
453        // 设置测试环境
454        let _temp_dir = setup_test_environment();
455
456        let manager =
457            UpgradeStrategyManager::new("0.0.13.2".to_string(), true, create_test_manifest());
458
459        // 强制全量升级,即使可以增量升级
460        let strategy = manager.determine_strategy().unwrap();
461
462        assert!(matches!(strategy, UpgradeStrategy::FullUpgrade { .. }));
463    }
464}