1
1
use flate2:: write:: GzEncoder ;
2
2
use flate2:: Compression ;
3
- use slog_scope:: info;
3
+ use mithril_common:: StdResult ;
4
+ use slog_scope:: { info, warn} ;
4
5
use std:: error:: Error as StdError ;
5
6
use std:: fs:: File ;
6
7
use std:: io;
7
8
use std:: path:: { Path , PathBuf } ;
8
9
use std:: sync:: RwLock ;
9
10
use thiserror:: Error ;
10
11
12
+ use crate :: dependency_injection:: DependenciesBuilderError ;
13
+
11
14
/// Define the ability to create snapshots.
12
15
pub trait Snapshotter : Sync + Send {
13
16
/// Create a new snapshot with the given archive name.
@@ -33,6 +36,7 @@ impl OngoingSnapshot {
33
36
pub fn new ( filepath : PathBuf , filesize : u64 ) -> Self {
34
37
Self { filepath, filesize }
35
38
}
39
+
36
40
pub fn get_file_path ( & self ) -> & PathBuf {
37
41
& self . filepath
38
42
}
@@ -60,33 +64,69 @@ pub enum SnapshotError {
60
64
61
65
impl Snapshotter for GzipSnapshotter {
62
66
fn snapshot ( & self , archive_name : & str ) -> Result < OngoingSnapshot , SnapshotError > {
63
- let filepath = self . create_archive ( archive_name) ?;
64
- let filesize = std:: fs:: metadata ( & filepath)
65
- . map_err ( |e| SnapshotError :: GeneralError ( e. to_string ( ) ) ) ?
66
- . len ( ) ;
67
+ let archive_path = self . ongoing_snapshot_directory . join ( archive_name) ;
68
+ let filesize = self . create_archive ( & archive_path) . map_err ( |err| {
69
+ if archive_path. exists ( ) {
70
+ if let Err ( remove_error) = std:: fs:: remove_file ( & archive_path) {
71
+ warn ! (
72
+ " > Post snapshotter.snapshot failure, could not remove temporary archive at path: path:{}, err: {}" ,
73
+ archive_path. display( ) ,
74
+ remove_error
75
+ ) ;
76
+ }
77
+ }
78
+
79
+ err
80
+ } ) ?;
67
81
68
- Ok ( OngoingSnapshot { filepath, filesize } )
82
+ Ok ( OngoingSnapshot {
83
+ filepath : archive_path,
84
+ filesize,
85
+ } )
69
86
}
70
87
}
71
88
72
89
impl GzipSnapshotter {
73
90
/// Snapshotter factory
74
- pub fn new ( db_directory : PathBuf , ongoing_snapshot_directory : PathBuf ) -> Self {
75
- Self {
91
+ pub fn new (
92
+ db_directory : PathBuf ,
93
+ ongoing_snapshot_directory : PathBuf ,
94
+ ) -> StdResult < GzipSnapshotter > {
95
+ if ongoing_snapshot_directory. exists ( ) {
96
+ std:: fs:: remove_dir_all ( & ongoing_snapshot_directory) ?;
97
+ }
98
+
99
+ std:: fs:: create_dir ( & ongoing_snapshot_directory) . map_err ( |e| {
100
+ DependenciesBuilderError :: Initialization {
101
+ message : format ! (
102
+ "Cannot create snapshotter directory '{}'." ,
103
+ ongoing_snapshot_directory. display( )
104
+ ) ,
105
+ error : Some ( e. into ( ) ) ,
106
+ }
107
+ } ) ?;
108
+
109
+ Ok ( Self {
76
110
db_directory,
77
111
ongoing_snapshot_directory,
78
- }
112
+ } )
113
+ }
114
+
115
+ fn get_file_size ( filepath : & Path ) -> Result < u64 , SnapshotError > {
116
+ let res = std:: fs:: metadata ( filepath)
117
+ . map_err ( |e| SnapshotError :: GeneralError ( e. to_string ( ) ) ) ?
118
+ . len ( ) ;
119
+ Ok ( res)
79
120
}
80
121
81
- fn create_archive ( & self , archive_name : & str ) -> Result < PathBuf , SnapshotError > {
82
- let path = self . ongoing_snapshot_directory . join ( archive_name) ;
122
+ fn create_archive ( & self , archive_path : & Path ) -> Result < u64 , SnapshotError > {
83
123
info ! (
84
124
"compressing {} into {}" ,
85
125
self . db_directory. display( ) ,
86
- path . display( )
126
+ archive_path . display( )
87
127
) ;
88
128
89
- let tar_gz = File :: create ( & path ) . map_err ( SnapshotError :: CreateArchiveError ) ?;
129
+ let tar_gz = File :: create ( archive_path ) . map_err ( SnapshotError :: CreateArchiveError ) ?;
90
130
let enc = GzEncoder :: new ( tar_gz, Compression :: default ( ) ) ;
91
131
let mut tar = tar:: Builder :: new ( enc) ;
92
132
@@ -98,7 +138,9 @@ impl GzipSnapshotter {
98
138
. map_err ( SnapshotError :: CreateArchiveError ) ?;
99
139
gz. try_finish ( ) . map_err ( SnapshotError :: CreateArchiveError ) ?;
100
140
101
- Ok ( path)
141
+ let filesize = Self :: get_file_size ( archive_path) ?;
142
+
143
+ Ok ( filesize)
102
144
}
103
145
}
104
146
@@ -154,8 +196,23 @@ impl Snapshotter for DumbSnapshotter {
154
196
155
197
#[ cfg( test) ]
156
198
mod tests {
199
+ use std:: sync:: Arc ;
200
+
157
201
use super :: * ;
158
202
203
+ fn get_test_directory ( dir_name : & str ) -> PathBuf {
204
+ let test_dir = std:: env:: temp_dir ( )
205
+ . join ( "mithril_test" )
206
+ . join ( "snapshotter" )
207
+ . join ( dir_name) ;
208
+ if test_dir. exists ( ) {
209
+ std:: fs:: remove_dir_all ( & test_dir) . unwrap ( ) ;
210
+ }
211
+ std:: fs:: create_dir_all ( & test_dir) . unwrap ( ) ;
212
+
213
+ test_dir
214
+ }
215
+
159
216
#[ test]
160
217
fn test_dumb_snapshotter ( ) {
161
218
let snapshotter = DumbSnapshotter :: new ( ) ;
@@ -174,4 +231,62 @@ mod tests {
174
231
)
175
232
) ;
176
233
}
234
+
235
+ #[ test]
236
+ fn should_create_directory_if_does_not_exist ( ) {
237
+ let test_dir = get_test_directory ( "should_create_directory_if_does_not_exist" ) ;
238
+ let pending_snapshot_directory = test_dir. join ( "pending_snapshot" ) ;
239
+ let db_directory = std:: env:: temp_dir ( ) . join ( "whatever" ) ;
240
+
241
+ Arc :: new ( GzipSnapshotter :: new ( db_directory, pending_snapshot_directory. clone ( ) ) . unwrap ( ) ) ;
242
+
243
+ assert ! ( pending_snapshot_directory. is_dir( ) ) ;
244
+ }
245
+
246
+ #[ test]
247
+ fn should_clean_pending_snapshot_directory_if_already_exists ( ) {
248
+ let test_dir =
249
+ get_test_directory ( "should_clean_pending_snapshot_directory_if_already_exists" ) ;
250
+ let pending_snapshot_directory = test_dir. join ( "pending_snapshot" ) ;
251
+ let db_directory = std:: env:: temp_dir ( ) . join ( "whatever" ) ;
252
+
253
+ std:: fs:: create_dir_all ( & pending_snapshot_directory) . unwrap ( ) ;
254
+
255
+ std:: fs:: File :: create ( pending_snapshot_directory. join ( "whatever.txt" ) ) . unwrap ( ) ;
256
+
257
+ Arc :: new ( GzipSnapshotter :: new ( db_directory, pending_snapshot_directory. clone ( ) ) . unwrap ( ) ) ;
258
+
259
+ assert_eq ! (
260
+ 0 ,
261
+ std:: fs:: read_dir( pending_snapshot_directory)
262
+ . unwrap( )
263
+ . count( )
264
+ ) ;
265
+ }
266
+
267
+ #[ test]
268
+ fn should_delete_tmp_file_in_pending_snapshot_directory_if_snapshotting_fail ( ) {
269
+ let test_dir = get_test_directory (
270
+ "should_delete_tmp_file_in_pending_snapshot_directory_if_snapshotting_fail" ,
271
+ ) ;
272
+ let pending_snapshot_directory = test_dir. join ( "pending_snapshot" ) ;
273
+ let db_directory = test_dir. join ( "db" ) ;
274
+
275
+ let snapshotter = Arc :: new (
276
+ GzipSnapshotter :: new ( db_directory, pending_snapshot_directory. clone ( ) ) . unwrap ( ) ,
277
+ ) ;
278
+
279
+ // this file should not be deleted by the archive creation
280
+ std:: fs:: File :: create ( pending_snapshot_directory. join ( "other-process.file" ) ) . unwrap ( ) ;
281
+
282
+ let _ = snapshotter
283
+ . snapshot ( "whatever.tar.gz" )
284
+ . expect_err ( "Snapshotter::snapshot should fail if the db is empty." ) ;
285
+ let remaining_files: Vec < String > = std:: fs:: read_dir ( & pending_snapshot_directory)
286
+ . unwrap ( )
287
+ . map ( |f| f. unwrap ( ) . file_name ( ) . to_str ( ) . unwrap ( ) . to_owned ( ) )
288
+ . collect ( ) ;
289
+
290
+ assert_eq ! ( vec![ "other-process.file" . to_string( ) ] , remaining_files) ;
291
+ }
177
292
}
0 commit comments