1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use async_trait::async_trait;
use slog::{debug, Logger};
use std::path::Path;

use mithril_common::logging::LoggerExtensions;
use mithril_common::StdResult;

use crate::snapshot_uploaders::{SnapshotLocation, SnapshotUploader};
use crate::tools::RemoteFileUploader;

/// GCPSnapshotUploader is a snapshot uploader working using Google Cloud Platform services
pub struct RemoteSnapshotUploader {
    bucket: String,
    file_uploader: Box<dyn RemoteFileUploader>,
    use_cdn_domain: bool,
    logger: Logger,
}

impl RemoteSnapshotUploader {
    /// GCPSnapshotUploader factory
    pub fn new(
        file_uploader: Box<dyn RemoteFileUploader>,
        bucket: String,
        use_cdn_domain: bool,
        logger: Logger,
    ) -> Self {
        let logger = logger.new_with_component_name::<Self>();
        debug!(logger, "New GCPSnapshotUploader created");
        Self {
            bucket,
            file_uploader,
            use_cdn_domain,
            logger,
        }
    }
}

#[async_trait]
impl SnapshotUploader for RemoteSnapshotUploader {
    async fn upload_snapshot(&self, snapshot_filepath: &Path) -> StdResult<SnapshotLocation> {
        let archive_name = snapshot_filepath.file_name().unwrap().to_str().unwrap();
        let location = if self.use_cdn_domain {
            format!("https://{}/{}", self.bucket, archive_name)
        } else {
            format!(
                "https://storage.googleapis.com/{}/{}",
                self.bucket, archive_name
            )
        };

        debug!(self.logger, "Uploading snapshot to remote storage"; "location" => &location);
        self.file_uploader.upload_file(snapshot_filepath).await?;
        debug!(self.logger, "Snapshot upload to remote storage completed"; "location" => &location);

        Ok(location)
    }
}

#[cfg(test)]
mod tests {
    use anyhow::anyhow;
    use std::path::Path;

    use crate::snapshot_uploaders::SnapshotUploader;
    use crate::test_tools::TestLogger;
    use crate::tools::MockRemoteFileUploader;

    use super::RemoteSnapshotUploader;

    #[tokio::test]
    async fn test_upload_snapshot_not_using_cdn_domain_ok() {
        let use_cdn_domain = false;
        let mut file_uploader = MockRemoteFileUploader::new();
        file_uploader.expect_upload_file().returning(|_| Ok(()));
        let snapshot_uploader = RemoteSnapshotUploader::new(
            Box::new(file_uploader),
            "cardano-testnet".to_string(),
            use_cdn_domain,
            TestLogger::stdout(),
        );
        let snapshot_filepath = Path::new("test/snapshot.xxx.tar.gz");
        let expected_location =
            "https://storage.googleapis.com/cardano-testnet/snapshot.xxx.tar.gz".to_string();

        let location = snapshot_uploader
            .upload_snapshot(snapshot_filepath)
            .await
            .expect("remote upload should not fail");

        assert_eq!(expected_location, location);
    }

    #[tokio::test]
    async fn test_upload_snapshot_using_cdn_domain_ok() {
        let use_cdn_domain = true;
        let mut file_uploader = MockRemoteFileUploader::new();
        file_uploader.expect_upload_file().returning(|_| Ok(()));
        let snapshot_uploader = RemoteSnapshotUploader::new(
            Box::new(file_uploader),
            "cdn.mithril.network".to_string(),
            use_cdn_domain,
            TestLogger::stdout(),
        );
        let snapshot_filepath = Path::new("test/snapshot.xxx.tar.gz");
        let expected_location = "https://cdn.mithril.network/snapshot.xxx.tar.gz".to_string();

        let location = snapshot_uploader
            .upload_snapshot(snapshot_filepath)
            .await
            .expect("remote upload should not fail");

        assert_eq!(expected_location, location);
    }

    #[tokio::test]
    async fn test_upload_snapshot_ko() {
        let mut file_uploader = MockRemoteFileUploader::new();
        file_uploader
            .expect_upload_file()
            .returning(|_| Err(anyhow!("unexpected error")));
        let snapshot_uploader = RemoteSnapshotUploader::new(
            Box::new(file_uploader),
            "".to_string(),
            false,
            TestLogger::stdout(),
        );
        let snapshot_filepath = Path::new("test/snapshot.xxx.tar.gz");

        let result = snapshot_uploader
            .upload_snapshot(snapshot_filepath)
            .await
            .expect_err("remote upload should fail");
        assert_eq!("unexpected error".to_string(), result.to_string());
    }
}