]>
Commit | Line | Data |
---|---|---|
15e9b4ed DM |
1 | use failure::*; |
2 | ||
184f17af | 3 | use crate::tools; |
ef2f2efb | 4 | use crate::api_schema::*; |
dc9a007b | 5 | use crate::api_schema::router::*; |
2085142e | 6 | //use crate::server::rest::*; |
15e9b4ed | 7 | use serde_json::{json, Value}; |
8f579717 DM |
8 | use std::collections::{HashSet, HashMap}; |
9 | use chrono::{DateTime, Datelike, Local}; | |
38b0dfa5 | 10 | use std::path::PathBuf; |
812c6f87 | 11 | use std::sync::Arc; |
15e9b4ed | 12 | |
2085142e DM |
13 | //use hyper::StatusCode; |
14 | //use hyper::rt::{Future, Stream}; | |
7e21da6e | 15 | |
15e9b4ed DM |
16 | use crate::config::datastore; |
17 | ||
e5064ba6 | 18 | use crate::backup::*; |
15e9b4ed | 19 | |
50cfb695 | 20 | mod catar; |
1629d2ad | 21 | |
8f579717 DM |
22 | fn group_backups(backup_list: Vec<BackupInfo>) -> HashMap<String, Vec<BackupInfo>> { |
23 | ||
24 | let mut group_hash = HashMap::new(); | |
25 | ||
26 | for info in backup_list { | |
184f17af | 27 | let group_id = info.backup_dir.group.group_path().to_str().unwrap().to_owned(); |
8f579717 DM |
28 | let time_list = group_hash.entry(group_id).or_insert(vec![]); |
29 | time_list.push(info); | |
30 | } | |
31 | ||
32 | group_hash | |
33 | } | |
34 | ||
35 | fn mark_selections<F: Fn(DateTime<Local>, &BackupInfo) -> String> ( | |
38b0dfa5 | 36 | mark: &mut HashSet<PathBuf>, |
8f579717 DM |
37 | list: &Vec<BackupInfo>, |
38 | keep: usize, | |
39 | select_id: F, | |
40 | ){ | |
41 | let mut hash = HashSet::new(); | |
42 | for info in list { | |
38b0dfa5 | 43 | let local_time = info.backup_dir.backup_time.with_timezone(&Local); |
8f579717 | 44 | if hash.len() >= keep as usize { break; } |
38b0dfa5 | 45 | let backup_id = info.backup_dir.relative_path(); |
8f579717 DM |
46 | let sel_id: String = select_id(local_time, &info); |
47 | if !hash.contains(&sel_id) { | |
48 | hash.insert(sel_id); | |
49 | //println!(" KEEP ID {} {}", backup_id, local_time.format("%c")); | |
50 | mark.insert(backup_id); | |
51 | } | |
52 | } | |
53 | } | |
54 | ||
ad20d198 | 55 | fn list_groups( |
812c6f87 DM |
56 | param: Value, |
57 | _info: &ApiMethod, | |
58 | _rpcenv: &mut RpcEnvironment, | |
59 | ) -> Result<Value, Error> { | |
60 | ||
61 | let store = param["store"].as_str().unwrap(); | |
62 | ||
63 | let datastore = DataStore::lookup_datastore(store)?; | |
64 | ||
65 | let backup_list = datastore.list_backups()?; | |
66 | ||
67 | let group_hash = group_backups(backup_list); | |
68 | ||
69 | let mut groups = vec![]; | |
70 | ||
71 | for (_group_id, mut list) in group_hash { | |
72 | ||
73 | list.sort_unstable_by(|a, b| b.backup_dir.backup_time.cmp(&a.backup_dir.backup_time)); // new backups first | |
74 | ||
75 | let info = &list[0]; | |
76 | let group = &info.backup_dir.group; | |
77 | ||
78 | groups.push(json!({ | |
ad20d198 DM |
79 | "backup-type": group.backup_type, |
80 | "backup-id": group.backup_id, | |
81 | "last-backup": info.backup_dir.backup_time.timestamp(), | |
82 | "backup-count": list.len() as u64, | |
83 | "files": info.files, | |
812c6f87 DM |
84 | })); |
85 | } | |
86 | ||
87 | Ok(json!(groups)) | |
88 | } | |
8f579717 | 89 | |
184f17af DM |
90 | fn list_snapshots ( |
91 | param: Value, | |
92 | _info: &ApiMethod, | |
93 | _rpcenv: &mut RpcEnvironment, | |
94 | ) -> Result<Value, Error> { | |
95 | ||
96 | let store = tools::required_string_param(¶m, "store")?; | |
97 | let backup_type = tools::required_string_param(¶m, "backup-type")?; | |
98 | let backup_id = tools::required_string_param(¶m, "backup-id")?; | |
99 | ||
100 | let group = BackupGroup { | |
101 | backup_type: backup_type.to_owned(), | |
102 | backup_id: backup_id.to_owned(), | |
103 | }; | |
104 | ||
105 | let datastore = DataStore::lookup_datastore(store)?; | |
106 | ||
107 | let backup_list = datastore.list_backups()?; | |
108 | ||
109 | let mut group_hash = group_backups(backup_list); | |
110 | ||
111 | let group_id = group.group_path().to_str().unwrap().to_owned(); | |
112 | ||
113 | let group_snapshots = match group_hash.get_mut(&group_id) { | |
114 | Some(data) => { | |
115 | // new backups first | |
116 | data.sort_unstable_by(|a, b| b.backup_dir.backup_time.cmp(&a.backup_dir.backup_time)); | |
117 | data | |
118 | } | |
119 | None => bail!("Backup group '{}' does not exists.", group_id), | |
120 | }; | |
121 | ||
122 | let mut snapshots = vec![]; | |
123 | ||
124 | for info in group_snapshots { | |
125 | ||
126 | let group = &info.backup_dir.group; | |
127 | ||
128 | snapshots.push(json!({ | |
129 | "backup-type": group.backup_type, | |
130 | "backup-id": group.backup_id, | |
131 | "backup-time": info.backup_dir.backup_time.timestamp(), | |
132 | "files": info.files, | |
133 | })); | |
134 | } | |
135 | ||
136 | Ok(json!(snapshots)) | |
137 | } | |
138 | ||
83b7db02 DM |
139 | fn prune( |
140 | param: Value, | |
141 | _info: &ApiMethod, | |
142 | _rpcenv: &mut RpcEnvironment, | |
143 | ) -> Result<Value, Error> { | |
144 | ||
145 | let store = param["store"].as_str().unwrap(); | |
146 | ||
147 | let datastore = DataStore::lookup_datastore(store)?; | |
148 | ||
149 | println!("Starting prune on store {}", store); | |
150 | ||
8f579717 DM |
151 | let backup_list = datastore.list_backups()?; |
152 | ||
153 | let group_hash = group_backups(backup_list); | |
154 | ||
155 | for (_group_id, mut list) in group_hash { | |
156 | ||
157 | let mut mark = HashSet::new(); | |
158 | ||
38b0dfa5 | 159 | list.sort_unstable_by(|a, b| b.backup_dir.backup_time.cmp(&a.backup_dir.backup_time)); // new backups first |
8f579717 DM |
160 | |
161 | if let Some(keep_last) = param["keep-last"].as_u64() { | |
162 | list.iter().take(keep_last as usize).for_each(|info| { | |
38b0dfa5 | 163 | mark.insert(info.backup_dir.relative_path()); |
8f579717 DM |
164 | }); |
165 | } | |
166 | ||
167 | if let Some(keep_daily) = param["keep-daily"].as_u64() { | |
168 | mark_selections(&mut mark, &list, keep_daily as usize, |local_time, _info| { | |
169 | format!("{}/{}/{}", local_time.year(), local_time.month(), local_time.day()) | |
170 | }); | |
171 | } | |
83b7db02 | 172 | |
8f579717 DM |
173 | if let Some(keep_weekly) = param["keep-weekly"].as_u64() { |
174 | mark_selections(&mut mark, &list, keep_weekly as usize, |local_time, _info| { | |
175 | format!("{}/{}", local_time.year(), local_time.iso_week().week()) | |
176 | }); | |
177 | } | |
178 | ||
179 | if let Some(keep_monthly) = param["keep-monthly"].as_u64() { | |
180 | mark_selections(&mut mark, &list, keep_monthly as usize, |local_time, _info| { | |
181 | format!("{}/{}", local_time.year(), local_time.month()) | |
182 | }); | |
183 | } | |
184 | ||
185 | if let Some(keep_yearly) = param["keep-yearly"].as_u64() { | |
186 | mark_selections(&mut mark, &list, keep_yearly as usize, |local_time, _info| { | |
187 | format!("{}/{}", local_time.year(), local_time.year()) | |
188 | }); | |
189 | } | |
190 | ||
38b0dfa5 DM |
191 | let mut remove_list: Vec<&BackupInfo> = list.iter() |
192 | .filter(|info| !mark.contains(&info.backup_dir.relative_path())).collect(); | |
8f579717 | 193 | |
38b0dfa5 | 194 | remove_list.sort_unstable_by(|a, b| a.backup_dir.backup_time.cmp(&b.backup_dir.backup_time)); // oldest backups first |
8f579717 DM |
195 | |
196 | for info in remove_list { | |
38b0dfa5 | 197 | datastore.remove_backup_dir(&info.backup_dir)?; |
8f579717 DM |
198 | } |
199 | } | |
83b7db02 DM |
200 | |
201 | Ok(json!(null)) | |
202 | } | |
203 | ||
204 | pub fn add_common_prune_prameters(schema: ObjectSchema) -> ObjectSchema { | |
205 | ||
206 | schema | |
8f579717 DM |
207 | .optional( |
208 | "keep-last", | |
209 | IntegerSchema::new("Number of backups to keep.") | |
210 | .minimum(1) | |
211 | ) | |
83b7db02 DM |
212 | .optional( |
213 | "keep-daily", | |
8f579717 DM |
214 | IntegerSchema::new("Number of daily backups to keep.") |
215 | .minimum(1) | |
216 | ) | |
217 | .optional( | |
218 | "keep-weekly", | |
219 | IntegerSchema::new("Number of weekly backups to keep.") | |
220 | .minimum(1) | |
221 | ) | |
222 | .optional( | |
223 | "keep-monthly", | |
224 | IntegerSchema::new("Number of monthly backups to keep.") | |
225 | .minimum(1) | |
226 | ) | |
227 | .optional( | |
228 | "keep-yearly", | |
229 | IntegerSchema::new("Number of yearly backups to keep.") | |
230 | .minimum(1) | |
83b7db02 DM |
231 | ) |
232 | } | |
233 | ||
234 | fn api_method_prune() -> ApiMethod { | |
235 | ApiMethod::new( | |
236 | prune, | |
237 | add_common_prune_prameters( | |
238 | ObjectSchema::new("Prune the datastore.") | |
239 | .required( | |
240 | "store", | |
241 | StringSchema::new("Datastore name.") | |
242 | ) | |
243 | ) | |
244 | ) | |
245 | } | |
246 | ||
15e9b4ed | 247 | // this is just a test for mutability/mutex handling - will remove later |
6049b71f DM |
248 | fn start_garbage_collection( |
249 | param: Value, | |
250 | _info: &ApiMethod, | |
251 | _rpcenv: &mut RpcEnvironment, | |
252 | ) -> Result<Value, Error> { | |
15e9b4ed | 253 | |
5a778d92 | 254 | let store = param["store"].as_str().unwrap(); |
15e9b4ed | 255 | |
5a778d92 | 256 | let datastore = DataStore::lookup_datastore(store)?; |
15e9b4ed | 257 | |
5a778d92 | 258 | println!("Starting garbage collection on store {}", store); |
15e9b4ed DM |
259 | |
260 | datastore.garbage_collection()?; | |
261 | ||
262 | Ok(json!(null)) | |
263 | } | |
264 | ||
691c89a0 DM |
265 | pub fn api_method_start_garbage_collection() -> ApiMethod { |
266 | ApiMethod::new( | |
267 | start_garbage_collection, | |
268 | ObjectSchema::new("Start garbage collection.") | |
5a778d92 | 269 | .required("store", StringSchema::new("Datastore name.")) |
691c89a0 DM |
270 | ) |
271 | } | |
272 | ||
6049b71f DM |
273 | fn garbage_collection_status( |
274 | param: Value, | |
275 | _info: &ApiMethod, | |
276 | _rpcenv: &mut RpcEnvironment, | |
277 | ) -> Result<Value, Error> { | |
691c89a0 | 278 | |
5a778d92 | 279 | let store = param["store"].as_str().unwrap(); |
691c89a0 | 280 | |
5a778d92 | 281 | println!("Garbage collection status on store {}", store); |
691c89a0 DM |
282 | |
283 | Ok(json!(null)) | |
284 | ||
285 | } | |
286 | ||
287 | pub fn api_method_garbage_collection_status() -> ApiMethod { | |
288 | ApiMethod::new( | |
289 | garbage_collection_status, | |
290 | ObjectSchema::new("Garbage collection status.") | |
5a778d92 | 291 | .required("store", StringSchema::new("Datastore name.")) |
691c89a0 DM |
292 | ) |
293 | } | |
294 | ||
6049b71f DM |
295 | fn get_backup_list( |
296 | param: Value, | |
297 | _info: &ApiMethod, | |
298 | _rpcenv: &mut RpcEnvironment, | |
299 | ) -> Result<Value, Error> { | |
83dbd80b | 300 | |
9f49fe1d | 301 | //let config = datastore::config()?; |
83dbd80b DM |
302 | |
303 | let store = param["store"].as_str().unwrap(); | |
304 | ||
305 | let datastore = DataStore::lookup_datastore(store)?; | |
306 | ||
307 | let mut list = vec![]; | |
308 | ||
309 | for info in datastore.list_backups()? { | |
310 | list.push(json!({ | |
dc4c09fa DM |
311 | "backup_type": info.backup_dir.group.backup_type, |
312 | "backup_id": info.backup_dir.group.backup_id, | |
38b0dfa5 | 313 | "backup_time": info.backup_dir.backup_time.timestamp(), |
8c75372b | 314 | "files": info.files, |
83dbd80b DM |
315 | })); |
316 | } | |
317 | ||
318 | let result = json!(list); | |
319 | ||
320 | Ok(result) | |
321 | } | |
7e21da6e | 322 | |
6049b71f DM |
323 | fn get_datastore_list( |
324 | _param: Value, | |
325 | _info: &ApiMethod, | |
326 | _rpcenv: &mut RpcEnvironment, | |
327 | ) -> Result<Value, Error> { | |
15e9b4ed DM |
328 | |
329 | let config = datastore::config()?; | |
330 | ||
5a778d92 | 331 | Ok(config.convert_to_array("store")) |
15e9b4ed DM |
332 | } |
333 | ||
691c89a0 | 334 | |
15e9b4ed DM |
335 | pub fn router() -> Router { |
336 | ||
812c6f87 DM |
337 | let store_schema: Arc<Schema> = Arc::new( |
338 | StringSchema::new("Datastore name.").into() | |
339 | ); | |
340 | ||
15e9b4ed DM |
341 | let datastore_info = Router::new() |
342 | .get(ApiMethod::new( | |
6049b71f | 343 | |_,_,_| Ok(json!([ |
83dbd80b | 344 | {"subdir": "backups" }, |
29f34b8e | 345 | {"subdir": "catar" }, |
83b7db02 | 346 | {"subdir": "gc" }, |
812c6f87 | 347 | {"subdir": "groups" }, |
184f17af | 348 | {"subdir": "snapshots" }, |
83b7db02 DM |
349 | {"subdir": "status" }, |
350 | {"subdir": "prune" }, | |
351 | ])), | |
15e9b4ed | 352 | ObjectSchema::new("Directory index.") |
812c6f87 | 353 | .required("store", store_schema.clone())) |
15e9b4ed | 354 | ) |
83dbd80b DM |
355 | .subdir( |
356 | "backups", | |
357 | Router::new() | |
358 | .get(ApiMethod::new( | |
359 | get_backup_list, | |
360 | ObjectSchema::new("List backups.") | |
812c6f87 | 361 | .required("store", store_schema.clone())))) |
264f52cf | 362 | .subdir( |
50cfb695 | 363 | "catar", |
264f52cf | 364 | Router::new() |
50cfb695 DM |
365 | .download(catar::api_method_download_catar()) |
366 | .upload(catar::api_method_upload_catar())) | |
15e9b4ed DM |
367 | .subdir( |
368 | "gc", | |
369 | Router::new() | |
691c89a0 | 370 | .get(api_method_garbage_collection_status()) |
83b7db02 | 371 | .post(api_method_start_garbage_collection())) |
812c6f87 DM |
372 | .subdir( |
373 | "groups", | |
374 | Router::new() | |
375 | .get(ApiMethod::new( | |
ad20d198 | 376 | list_groups, |
812c6f87 DM |
377 | ObjectSchema::new("List backup groups.") |
378 | .required("store", store_schema.clone())))) | |
184f17af DM |
379 | .subdir( |
380 | "snapshots", | |
381 | Router::new() | |
382 | .get(ApiMethod::new( | |
383 | list_snapshots, | |
384 | ObjectSchema::new("List backup groups.") | |
385 | .required("store", store_schema.clone()) | |
386 | .required("backup-type", StringSchema::new("Backup type.")) | |
387 | .required("backup-id", StringSchema::new("Backup ID."))))) | |
83b7db02 DM |
388 | .subdir( |
389 | "prune", | |
390 | Router::new() | |
391 | .post(api_method_prune())); | |
7e21da6e | 392 | |
15e9b4ed DM |
393 | |
394 | ||
395 | let route = Router::new() | |
396 | .get(ApiMethod::new( | |
397 | get_datastore_list, | |
398 | ObjectSchema::new("Directory index."))) | |
5a778d92 | 399 | .match_all("store", datastore_info); |
15e9b4ed DM |
400 | |
401 | ||
402 | ||
403 | route | |
404 | } |