]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/admin/datastore.rs
src/api2/admin/datastore.rs: rename get_group_list to list_groups, cleanups
[proxmox-backup.git] / src / api2 / admin / datastore.rs
CommitLineData
15e9b4ed
DM
1use failure::*;
2
184f17af 3use crate::tools;
ef2f2efb 4use crate::api_schema::*;
dc9a007b 5use crate::api_schema::router::*;
2085142e 6//use crate::server::rest::*;
15e9b4ed 7use serde_json::{json, Value};
8f579717
DM
8use std::collections::{HashSet, HashMap};
9use chrono::{DateTime, Datelike, Local};
38b0dfa5 10use std::path::PathBuf;
812c6f87 11use std::sync::Arc;
15e9b4ed 12
2085142e
DM
13//use hyper::StatusCode;
14//use hyper::rt::{Future, Stream};
7e21da6e 15
15e9b4ed
DM
16use crate::config::datastore;
17
e5064ba6 18use crate::backup::*;
15e9b4ed 19
50cfb695 20mod catar;
1629d2ad 21
8f579717
DM
22fn 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
35fn 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 55fn 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
90fn list_snapshots (
91 param: Value,
92 _info: &ApiMethod,
93 _rpcenv: &mut RpcEnvironment,
94) -> Result<Value, Error> {
95
96 let store = tools::required_string_param(&param, "store")?;
97 let backup_type = tools::required_string_param(&param, "backup-type")?;
98 let backup_id = tools::required_string_param(&param, "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
139fn 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
204pub 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
234fn 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
248fn 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
265pub 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
273fn 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
287pub 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
295fn 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
323fn 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
335pub 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}