]>
Commit | Line | Data |
---|---|---|
152764ec DM |
1 | use failure::*; |
2 | ||
72375ce6 | 3 | use std::sync::Arc; |
92ac375a DM |
4 | |
5 | use futures::*; | |
152764ec | 6 | use hyper::header::{HeaderValue, UPGRADE}; |
bd1507c4 | 7 | use hyper::{Body, Response, StatusCode}; |
152764ec | 8 | use hyper::http::request::Parts; |
0aadd40b | 9 | use chrono::{Local, TimeZone}; |
152764ec | 10 | |
f9578f3c | 11 | use serde_json::{json, Value}; |
152764ec | 12 | |
92ac375a | 13 | use crate::tools; |
d3611366 | 14 | use crate::tools::wrapped_reader_stream::*; |
152764ec DM |
15 | use crate::api_schema::router::*; |
16 | use crate::api_schema::*; | |
58c8d7d9 | 17 | use crate::server::WorkerTask; |
21ee7912 | 18 | use crate::backup::*; |
6762db70 | 19 | use crate::api2::types::*; |
152764ec | 20 | |
d95ced64 DM |
21 | mod environment; |
22 | use environment::*; | |
23 | ||
bd1507c4 DM |
24 | mod service; |
25 | use service::*; | |
26 | ||
21ee7912 DM |
27 | mod upload_chunk; |
28 | use upload_chunk::*; | |
29 | ||
7773ccc1 DM |
30 | pub fn router() -> Router { |
31 | Router::new() | |
32 | .upgrade(api_method_upgrade_backup()) | |
33 | } | |
21ee7912 | 34 | |
ca60c371 | 35 | pub fn api_method_upgrade_backup() -> ApiAsyncMethod { |
152764ec | 36 | ApiAsyncMethod::new( |
0aadd40b | 37 | upgrade_to_backup_protocol, |
986bef16 | 38 | ObjectSchema::new(concat!("Upgraded to backup protocol ('", PROXMOX_BACKUP_PROTOCOL_ID_V1!(), "').")) |
0aadd40b DM |
39 | .required("store", StringSchema::new("Datastore name.")) |
40 | .required("backup-type", StringSchema::new("Backup type.") | |
cc84a830 | 41 | .format(Arc::new(ApiStringFormat::Enum(&["vm", "ct", "host"])))) |
0aadd40b | 42 | .required("backup-id", StringSchema::new("Backup ID.")) |
a42d1f55 | 43 | .optional("debug", BooleanSchema::new("Enable verbose debug logging.")) |
152764ec DM |
44 | ) |
45 | } | |
46 | ||
0aadd40b | 47 | fn upgrade_to_backup_protocol( |
152764ec DM |
48 | parts: Parts, |
49 | req_body: Body, | |
0aadd40b | 50 | param: Value, |
152764ec | 51 | _info: &ApiAsyncMethod, |
dd5495d6 | 52 | rpcenv: Box<dyn RpcEnvironment>, |
152764ec | 53 | ) -> Result<BoxFut, Error> { |
0aadd40b | 54 | |
a42d1f55 DM |
55 | let debug = param["debug"].as_bool().unwrap_or(false); |
56 | ||
bb105f9d DM |
57 | let store = tools::required_string_param(¶m, "store")?.to_owned(); |
58 | let datastore = DataStore::lookup_datastore(&store)?; | |
21ee7912 | 59 | |
0aadd40b DM |
60 | let backup_type = tools::required_string_param(¶m, "backup-type")?; |
61 | let backup_id = tools::required_string_param(¶m, "backup-id")?; | |
f9578f3c | 62 | let backup_time = Local.timestamp(Local::now().timestamp(), 0); |
152764ec DM |
63 | |
64 | let protocols = parts | |
65 | .headers | |
66 | .get("UPGRADE") | |
67 | .ok_or_else(|| format_err!("missing Upgrade header"))? | |
68 | .to_str()?; | |
69 | ||
986bef16 | 70 | if protocols != PROXMOX_BACKUP_PROTOCOL_ID_V1!() { |
152764ec DM |
71 | bail!("invalid protocol name"); |
72 | } | |
73 | ||
96e95fc1 DM |
74 | if parts.version >= http::version::Version::HTTP_2 { |
75 | bail!("unexpected http version '{:?}' (expected version < 2)", parts.version); | |
76 | } | |
77 | ||
0aadd40b | 78 | let worker_id = format!("{}_{}_{}", store, backup_type, backup_id); |
d9bd06ea | 79 | |
58c8d7d9 DM |
80 | let username = rpcenv.get_user().unwrap(); |
81 | let env_type = rpcenv.env_type(); | |
92ac375a | 82 | |
51a4f63f | 83 | let backup_group = BackupGroup::new(backup_type, backup_id); |
fbb798f6 | 84 | let last_backup = BackupInfo::last_backup(&datastore.base_path(), &backup_group).unwrap_or(None); |
51a4f63f | 85 | let backup_dir = BackupDir::new_with_group(backup_group, backup_time.timestamp()); |
f9578f3c | 86 | |
bb105f9d | 87 | let (path, is_new) = datastore.create_backup_dir(&backup_dir)?; |
f9578f3c DM |
88 | if !is_new { bail!("backup directorty already exists."); } |
89 | ||
0aadd40b | 90 | WorkerTask::spawn("backup", Some(worker_id), &username.clone(), true, move |worker| { |
bb105f9d | 91 | let mut env = BackupEnvironment::new( |
6b95c7df | 92 | env_type, username.clone(), worker.clone(), datastore, backup_dir); |
b02a52e3 | 93 | |
a42d1f55 | 94 | env.debug = debug; |
bb105f9d | 95 | env.last_backup = last_backup; |
b02a52e3 | 96 | |
bb105f9d DM |
97 | env.log(format!("starting new backup on datastore '{}': {:?}", store, path)); |
98 | ||
a42d1f55 | 99 | let service = BackupService::new(env.clone(), worker.clone(), debug); |
72375ce6 | 100 | |
a66ab8ae DM |
101 | let abort_future = worker.abort_future(); |
102 | ||
372724af | 103 | let env2 = env.clone(); |
a42d1f55 | 104 | let env3 = env.clone(); |
bb105f9d | 105 | |
152764ec DM |
106 | req_body |
107 | .on_upgrade() | |
92ac375a | 108 | .map_err(Error::from) |
152764ec | 109 | .and_then(move |conn| { |
a42d1f55 | 110 | env3.debug("protocol upgrade done"); |
92ac375a DM |
111 | |
112 | let mut http = hyper::server::conn::Http::new(); | |
113 | http.http2_only(true); | |
adec8ea2 | 114 | // increase window size: todo - find optiomal size |
771953f9 DM |
115 | let window_size = 32*1024*1024; // max = (1 << 31) - 2 |
116 | http.http2_initial_stream_window_size(window_size); | |
117 | http.http2_initial_connection_window_size(window_size); | |
92ac375a | 118 | |
d9bd06ea DM |
119 | http.serve_connection(conn, service) |
120 | .map_err(Error::from) | |
0aadd40b | 121 | }) |
a66ab8ae | 122 | .select(abort_future.map_err(|_| {}).then(move |_| { bail!("task aborted"); })) |
372724af | 123 | .map_err(|(err, _)| err) |
bb105f9d | 124 | .and_then(move |(_result, _)| { |
372724af DM |
125 | env.ensure_finished()?; |
126 | env.log("backup finished sucessfully"); | |
bb105f9d DM |
127 | Ok(()) |
128 | }) | |
372724af DM |
129 | .then(move |result| { |
130 | if let Err(err) = result { | |
a9584932 | 131 | match env2.ensure_finished() { |
e3d525fe | 132 | Ok(()) => {}, // ignore error after finish |
a9584932 DM |
133 | _ => { |
134 | env2.log(format!("backup failed: {}", err)); | |
135 | env2.log("removing failed backup"); | |
136 | env2.remove_backup()?; | |
137 | return Err(err); | |
138 | } | |
139 | } | |
372724af DM |
140 | } |
141 | Ok(()) | |
bb105f9d | 142 | }) |
090ac9f7 DM |
143 | })?; |
144 | ||
145 | let response = Response::builder() | |
146 | .status(StatusCode::SWITCHING_PROTOCOLS) | |
986bef16 | 147 | .header(UPGRADE, HeaderValue::from_static(PROXMOX_BACKUP_PROTOCOL_ID_V1!())) |
090ac9f7 DM |
148 | .body(Body::empty())?; |
149 | ||
150 | Ok(Box::new(futures::future::ok(response))) | |
152764ec | 151 | } |
92ac375a | 152 | |
339ddfcb | 153 | pub fn backup_api() -> Router { |
92ac375a | 154 | |
92ac375a | 155 | let router = Router::new() |
39d6846e DM |
156 | .subdir( |
157 | "config", Router::new() | |
158 | .upload(api_method_upload_config()) | |
159 | ) | |
f9578f3c | 160 | .subdir( |
642322b4 DM |
161 | "dynamic_chunk", Router::new() |
162 | .upload(api_method_upload_dynamic_chunk()) | |
f9578f3c DM |
163 | ) |
164 | .subdir( | |
165 | "dynamic_index", Router::new() | |
d3611366 | 166 | .download(api_method_dynamic_chunk_index()) |
f9578f3c | 167 | .post(api_method_create_dynamic_index()) |
82ab7230 | 168 | .put(api_method_dynamic_append()) |
f9578f3c | 169 | ) |
a2077252 DM |
170 | .subdir( |
171 | "dynamic_close", Router::new() | |
172 | .post(api_method_close_dynamic_index()) | |
173 | ) | |
642322b4 DM |
174 | .subdir( |
175 | "fixed_chunk", Router::new() | |
176 | .upload(api_method_upload_fixed_chunk()) | |
177 | ) | |
a42fa400 DM |
178 | .subdir( |
179 | "fixed_index", Router::new() | |
180 | .download(api_method_fixed_chunk_index()) | |
181 | .post(api_method_create_fixed_index()) | |
182 | .put(api_method_fixed_append()) | |
183 | ) | |
184 | .subdir( | |
185 | "fixed_close", Router::new() | |
186 | .post(api_method_close_fixed_index()) | |
187 | ) | |
372724af DM |
188 | .subdir( |
189 | "finish", Router::new() | |
a55fcd74 | 190 | .post( |
372724af DM |
191 | ApiMethod::new( |
192 | finish_backup, | |
193 | ObjectSchema::new("Mark backup as finished.") | |
194 | ) | |
195 | ) | |
196 | ) | |
adec8ea2 DM |
197 | .subdir( |
198 | "speedtest", Router::new() | |
199 | .upload(api_method_upload_speedtest()) | |
200 | ) | |
92ac375a DM |
201 | .list_subdirs(); |
202 | ||
203 | router | |
204 | } | |
205 | ||
f9578f3c DM |
206 | pub fn api_method_create_dynamic_index() -> ApiMethod { |
207 | ApiMethod::new( | |
208 | create_dynamic_index, | |
209 | ObjectSchema::new("Create dynamic chunk index file.") | |
4e93f8c1 | 210 | .required("archive-name", crate::api2::types::BACKUP_ARCHIVE_NAME_SCHEMA.clone()) |
f9578f3c DM |
211 | ) |
212 | } | |
213 | ||
214 | fn create_dynamic_index( | |
215 | param: Value, | |
216 | _info: &ApiMethod, | |
dd5495d6 | 217 | rpcenv: &mut dyn RpcEnvironment, |
f9578f3c DM |
218 | ) -> Result<Value, Error> { |
219 | ||
220 | let env: &BackupEnvironment = rpcenv.as_ref(); | |
f9578f3c | 221 | |
8bea85b4 | 222 | let name = tools::required_string_param(¶m, "archive-name")?.to_owned(); |
f9578f3c | 223 | |
8bea85b4 | 224 | let mut archive_name = name.clone(); |
f9578f3c | 225 | if !archive_name.ends_with(".pxar") { |
a42fa400 | 226 | bail!("wrong archive extension: '{}'", archive_name); |
f9578f3c DM |
227 | } else { |
228 | archive_name.push_str(".didx"); | |
229 | } | |
230 | ||
6b95c7df | 231 | let mut path = env.backup_dir.relative_path(); |
f9578f3c DM |
232 | path.push(archive_name); |
233 | ||
976595e1 | 234 | let index = env.datastore.create_dynamic_writer(&path)?; |
8bea85b4 | 235 | let wid = env.register_dynamic_writer(index, name)?; |
f9578f3c | 236 | |
bb105f9d | 237 | env.log(format!("created new dynamic index {} ({:?})", wid, path)); |
f9578f3c | 238 | |
bb105f9d | 239 | Ok(json!(wid)) |
f9578f3c DM |
240 | } |
241 | ||
a42fa400 DM |
242 | pub fn api_method_create_fixed_index() -> ApiMethod { |
243 | ApiMethod::new( | |
244 | create_fixed_index, | |
245 | ObjectSchema::new("Create fixed chunk index file.") | |
246 | .required("archive-name", crate::api2::types::BACKUP_ARCHIVE_NAME_SCHEMA.clone()) | |
247 | .required("size", IntegerSchema::new("File size.") | |
248 | .minimum(1) | |
249 | ) | |
250 | ) | |
251 | } | |
252 | ||
253 | fn create_fixed_index( | |
254 | param: Value, | |
255 | _info: &ApiMethod, | |
dd5495d6 | 256 | rpcenv: &mut dyn RpcEnvironment, |
a42fa400 DM |
257 | ) -> Result<Value, Error> { |
258 | ||
259 | let env: &BackupEnvironment = rpcenv.as_ref(); | |
260 | ||
261 | println!("PARAM: {:?}", param); | |
262 | ||
263 | let name = tools::required_string_param(¶m, "archive-name")?.to_owned(); | |
264 | let size = tools::required_integer_param(¶m, "size")? as usize; | |
265 | ||
266 | let mut archive_name = name.clone(); | |
267 | if !archive_name.ends_with(".img") { | |
268 | bail!("wrong archive extension: '{}'", archive_name); | |
269 | } else { | |
270 | archive_name.push_str(".fidx"); | |
271 | } | |
272 | ||
273 | let mut path = env.backup_dir.relative_path(); | |
274 | path.push(archive_name); | |
275 | ||
276 | let chunk_size = 4096*1024; // todo: ?? | |
277 | ||
278 | let index = env.datastore.create_fixed_writer(&path, size, chunk_size)?; | |
279 | let wid = env.register_fixed_writer(index, name, size, chunk_size as u32)?; | |
280 | ||
281 | env.log(format!("created new fixed index {} ({:?})", wid, path)); | |
282 | ||
283 | Ok(json!(wid)) | |
284 | } | |
285 | ||
82ab7230 DM |
286 | pub fn api_method_dynamic_append() -> ApiMethod { |
287 | ApiMethod::new( | |
288 | dynamic_append, | |
289 | ObjectSchema::new("Append chunk to dynamic index writer.") | |
417cb073 DM |
290 | .required("wid", IntegerSchema::new("Dynamic writer ID.") |
291 | .minimum(1) | |
292 | .maximum(256) | |
293 | ) | |
aa1b2e04 | 294 | .required("digest-list", ArraySchema::new( |
6762db70 | 295 | "Chunk digest list.", CHUNK_DIGEST_SCHEMA.clone()) |
aa1b2e04 | 296 | ) |
417cb073 DM |
297 | .required("offset-list", ArraySchema::new( |
298 | "Chunk offset list.", | |
299 | IntegerSchema::new("Corresponding chunk offsets.") | |
300 | .minimum(0) | |
301 | .into()) | |
82ab7230 DM |
302 | ) |
303 | ) | |
304 | } | |
305 | ||
306 | fn dynamic_append ( | |
307 | param: Value, | |
308 | _info: &ApiMethod, | |
dd5495d6 | 309 | rpcenv: &mut dyn RpcEnvironment, |
82ab7230 DM |
310 | ) -> Result<Value, Error> { |
311 | ||
312 | let wid = tools::required_integer_param(¶m, "wid")? as usize; | |
aa1b2e04 | 313 | let digest_list = tools::required_array_param(¶m, "digest-list")?; |
417cb073 | 314 | let offset_list = tools::required_array_param(¶m, "offset-list")?; |
aa1b2e04 | 315 | |
417cb073 DM |
316 | if offset_list.len() != digest_list.len() { |
317 | bail!("offset list has wrong length ({} != {})", offset_list.len(), digest_list.len()); | |
318 | } | |
319 | ||
82ab7230 DM |
320 | let env: &BackupEnvironment = rpcenv.as_ref(); |
321 | ||
39e60bd6 DM |
322 | env.debug(format!("dynamic_append {} chunks", digest_list.len())); |
323 | ||
417cb073 | 324 | for (i, item) in digest_list.iter().enumerate() { |
aa1b2e04 DM |
325 | let digest_str = item.as_str().unwrap(); |
326 | let digest = crate::tools::hex_to_digest(digest_str)?; | |
417cb073 | 327 | let offset = offset_list[i].as_u64().unwrap(); |
aa1b2e04 | 328 | let size = env.lookup_chunk(&digest).ok_or_else(|| format_err!("no such chunk {}", digest_str))?; |
39e60bd6 | 329 | |
417cb073 | 330 | env.dynamic_writer_append_chunk(wid, offset, size, &digest)?; |
82ab7230 | 331 | |
39e60bd6 | 332 | env.debug(format!("sucessfully added chunk {} to dynamic index {} (offset {}, size {})", digest_str, wid, offset, size)); |
aa1b2e04 | 333 | } |
82ab7230 DM |
334 | |
335 | Ok(Value::Null) | |
336 | } | |
337 | ||
a42fa400 DM |
338 | pub fn api_method_fixed_append() -> ApiMethod { |
339 | ApiMethod::new( | |
340 | fixed_append, | |
341 | ObjectSchema::new("Append chunk to fixed index writer.") | |
342 | .required("wid", IntegerSchema::new("Fixed writer ID.") | |
343 | .minimum(1) | |
344 | .maximum(256) | |
345 | ) | |
346 | .required("digest-list", ArraySchema::new( | |
6762db70 | 347 | "Chunk digest list.", CHUNK_DIGEST_SCHEMA.clone()) |
a42fa400 DM |
348 | ) |
349 | .required("offset-list", ArraySchema::new( | |
350 | "Chunk offset list.", | |
351 | IntegerSchema::new("Corresponding chunk offsets.") | |
352 | .minimum(0) | |
353 | .into()) | |
354 | ) | |
355 | ) | |
356 | } | |
357 | ||
358 | fn fixed_append ( | |
359 | param: Value, | |
360 | _info: &ApiMethod, | |
dd5495d6 | 361 | rpcenv: &mut dyn RpcEnvironment, |
a42fa400 DM |
362 | ) -> Result<Value, Error> { |
363 | ||
364 | let wid = tools::required_integer_param(¶m, "wid")? as usize; | |
365 | let digest_list = tools::required_array_param(¶m, "digest-list")?; | |
366 | let offset_list = tools::required_array_param(¶m, "offset-list")?; | |
367 | ||
a42fa400 DM |
368 | if offset_list.len() != digest_list.len() { |
369 | bail!("offset list has wrong length ({} != {})", offset_list.len(), digest_list.len()); | |
370 | } | |
371 | ||
372 | let env: &BackupEnvironment = rpcenv.as_ref(); | |
373 | ||
39e60bd6 DM |
374 | env.debug(format!("fixed_append {} chunks", digest_list.len())); |
375 | ||
a42fa400 DM |
376 | for (i, item) in digest_list.iter().enumerate() { |
377 | let digest_str = item.as_str().unwrap(); | |
378 | let digest = crate::tools::hex_to_digest(digest_str)?; | |
379 | let offset = offset_list[i].as_u64().unwrap(); | |
380 | let size = env.lookup_chunk(&digest).ok_or_else(|| format_err!("no such chunk {}", digest_str))?; | |
39e60bd6 | 381 | |
a42fa400 DM |
382 | env.fixed_writer_append_chunk(wid, offset, size, &digest)?; |
383 | ||
39e60bd6 | 384 | env.debug(format!("sucessfully added chunk {} to fixed index {} (offset {}, size {})", digest_str, wid, offset, size)); |
a42fa400 DM |
385 | } |
386 | ||
387 | Ok(Value::Null) | |
388 | } | |
389 | ||
a2077252 DM |
390 | pub fn api_method_close_dynamic_index() -> ApiMethod { |
391 | ApiMethod::new( | |
392 | close_dynamic_index, | |
393 | ObjectSchema::new("Close dynamic index writer.") | |
394 | .required("wid", IntegerSchema::new("Dynamic writer ID.") | |
395 | .minimum(1) | |
396 | .maximum(256) | |
397 | ) | |
8bea85b4 DM |
398 | .required("chunk-count", IntegerSchema::new("Chunk count. This is used to verify that the server got all chunks.") |
399 | .minimum(1) | |
400 | ) | |
401 | .required("size", IntegerSchema::new("File size. This is used to verify that the server got all data.") | |
402 | .minimum(1) | |
403 | ) | |
a2077252 DM |
404 | ) |
405 | } | |
406 | ||
407 | fn close_dynamic_index ( | |
408 | param: Value, | |
409 | _info: &ApiMethod, | |
dd5495d6 | 410 | rpcenv: &mut dyn RpcEnvironment, |
a2077252 DM |
411 | ) -> Result<Value, Error> { |
412 | ||
413 | let wid = tools::required_integer_param(¶m, "wid")? as usize; | |
8bea85b4 DM |
414 | let chunk_count = tools::required_integer_param(¶m, "chunk-count")? as u64; |
415 | let size = tools::required_integer_param(¶m, "size")? as u64; | |
a2077252 DM |
416 | |
417 | let env: &BackupEnvironment = rpcenv.as_ref(); | |
418 | ||
8bea85b4 | 419 | env.dynamic_writer_close(wid, chunk_count, size)?; |
a2077252 | 420 | |
bb105f9d DM |
421 | env.log(format!("sucessfully closed dynamic index {}", wid)); |
422 | ||
a2077252 DM |
423 | Ok(Value::Null) |
424 | } | |
425 | ||
a42fa400 DM |
426 | pub fn api_method_close_fixed_index() -> ApiMethod { |
427 | ApiMethod::new( | |
428 | close_fixed_index, | |
429 | ObjectSchema::new("Close fixed index writer.") | |
430 | .required("wid", IntegerSchema::new("Fixed writer ID.") | |
431 | .minimum(1) | |
432 | .maximum(256) | |
433 | ) | |
434 | .required("chunk-count", IntegerSchema::new("Chunk count. This is used to verify that the server got all chunks.") | |
435 | .minimum(1) | |
436 | ) | |
437 | .required("size", IntegerSchema::new("File size. This is used to verify that the server got all data.") | |
438 | .minimum(1) | |
439 | ) | |
440 | ) | |
441 | } | |
442 | ||
443 | fn close_fixed_index ( | |
444 | param: Value, | |
445 | _info: &ApiMethod, | |
dd5495d6 | 446 | rpcenv: &mut dyn RpcEnvironment, |
a42fa400 DM |
447 | ) -> Result<Value, Error> { |
448 | ||
449 | let wid = tools::required_integer_param(¶m, "wid")? as usize; | |
450 | let chunk_count = tools::required_integer_param(¶m, "chunk-count")? as u64; | |
451 | let size = tools::required_integer_param(¶m, "size")? as u64; | |
452 | ||
453 | let env: &BackupEnvironment = rpcenv.as_ref(); | |
454 | ||
455 | env.fixed_writer_close(wid, chunk_count, size)?; | |
456 | ||
457 | env.log(format!("sucessfully closed fixed index {}", wid)); | |
458 | ||
459 | Ok(Value::Null) | |
460 | } | |
a2077252 | 461 | |
372724af DM |
462 | fn finish_backup ( |
463 | _param: Value, | |
464 | _info: &ApiMethod, | |
dd5495d6 | 465 | rpcenv: &mut dyn RpcEnvironment, |
372724af DM |
466 | ) -> Result<Value, Error> { |
467 | ||
468 | let env: &BackupEnvironment = rpcenv.as_ref(); | |
469 | ||
470 | env.finish_backup()?; | |
60e589a1 | 471 | env.log("sucessfully finished backup"); |
372724af DM |
472 | |
473 | Ok(Value::Null) | |
474 | } | |
a2077252 | 475 | |
a42fa400 DM |
476 | pub fn api_method_dynamic_chunk_index() -> ApiAsyncMethod { |
477 | ApiAsyncMethod::new( | |
478 | dynamic_chunk_index, | |
479 | ObjectSchema::new(r###" | |
480 | Download the dynamic chunk index from the previous backup. | |
481 | Simply returns an empty list if this is the first backup. | |
482 | "### | |
483 | ) | |
484 | .required("archive-name", crate::api2::types::BACKUP_ARCHIVE_NAME_SCHEMA.clone()) | |
485 | ) | |
486 | } | |
487 | ||
d3611366 DM |
488 | fn dynamic_chunk_index( |
489 | _parts: Parts, | |
490 | _req_body: Body, | |
491 | param: Value, | |
492 | _info: &ApiAsyncMethod, | |
dd5495d6 | 493 | rpcenv: Box<dyn RpcEnvironment>, |
d3611366 DM |
494 | ) -> Result<BoxFut, Error> { |
495 | ||
496 | let env: &BackupEnvironment = rpcenv.as_ref(); | |
d3611366 DM |
497 | |
498 | let mut archive_name = tools::required_string_param(¶m, "archive-name")?.to_owned(); | |
499 | ||
500 | if !archive_name.ends_with(".pxar") { | |
a42fa400 | 501 | bail!("wrong archive extension: '{}'", archive_name); |
d3611366 DM |
502 | } else { |
503 | archive_name.push_str(".didx"); | |
504 | } | |
505 | ||
a9584932 DM |
506 | let empty_response = { |
507 | Response::builder() | |
508 | .status(StatusCode::OK) | |
509 | .body(Body::empty())? | |
510 | }; | |
511 | ||
d3611366 DM |
512 | let last_backup = match &env.last_backup { |
513 | Some(info) => info, | |
a9584932 | 514 | None => return Ok(Box::new(future::ok(empty_response))), |
d3611366 DM |
515 | }; |
516 | ||
517 | let mut path = last_backup.backup_dir.relative_path(); | |
a9584932 | 518 | path.push(&archive_name); |
d3611366 | 519 | |
a9584932 DM |
520 | let index = match env.datastore.open_dynamic_reader(path) { |
521 | Ok(index) => index, | |
522 | Err(_) => { | |
523 | env.log(format!("there is no last backup for archive '{}'", archive_name)); | |
524 | return Ok(Box::new(future::ok(empty_response))); | |
525 | } | |
526 | }; | |
527 | ||
39e60bd6 DM |
528 | env.log(format!("download last backup index for archive '{}'", archive_name)); |
529 | ||
a9584932 DM |
530 | let count = index.index_count(); |
531 | for pos in 0..count { | |
532 | let (start, end, digest) = index.chunk_info(pos)?; | |
533 | let size = (end - start) as u32; | |
534 | env.register_chunk(digest, size)?; | |
535 | } | |
d3611366 | 536 | |
7f3d2ffa | 537 | let reader = DigestListEncoder::new(Box::new(index)); |
d3611366 DM |
538 | |
539 | let stream = WrappedReaderStream::new(reader); | |
540 | ||
541 | // fixme: set size, content type? | |
542 | let response = http::Response::builder() | |
543 | .status(200) | |
544 | .body(Body::wrap_stream(stream))?; | |
545 | ||
546 | Ok(Box::new(future::ok(response))) | |
547 | } | |
a42fa400 DM |
548 | |
549 | pub fn api_method_fixed_chunk_index() -> ApiAsyncMethod { | |
550 | ApiAsyncMethod::new( | |
551 | fixed_chunk_index, | |
552 | ObjectSchema::new(r###" | |
553 | Download the fixed chunk index from the previous backup. | |
554 | Simply returns an empty list if this is the first backup. | |
555 | "### | |
556 | ) | |
557 | .required("archive-name", crate::api2::types::BACKUP_ARCHIVE_NAME_SCHEMA.clone()) | |
558 | ) | |
559 | } | |
560 | ||
561 | fn fixed_chunk_index( | |
562 | _parts: Parts, | |
563 | _req_body: Body, | |
564 | param: Value, | |
565 | _info: &ApiAsyncMethod, | |
dd5495d6 | 566 | rpcenv: Box<dyn RpcEnvironment>, |
a42fa400 DM |
567 | ) -> Result<BoxFut, Error> { |
568 | ||
569 | let env: &BackupEnvironment = rpcenv.as_ref(); | |
570 | ||
571 | let mut archive_name = tools::required_string_param(¶m, "archive-name")?.to_owned(); | |
572 | ||
573 | if !archive_name.ends_with(".img") { | |
574 | bail!("wrong archive extension: '{}'", archive_name); | |
575 | } else { | |
576 | archive_name.push_str(".fidx"); | |
577 | } | |
578 | ||
579 | let empty_response = { | |
580 | Response::builder() | |
581 | .status(StatusCode::OK) | |
582 | .body(Body::empty())? | |
583 | }; | |
584 | ||
585 | let last_backup = match &env.last_backup { | |
586 | Some(info) => info, | |
587 | None => return Ok(Box::new(future::ok(empty_response))), | |
588 | }; | |
589 | ||
590 | let mut path = last_backup.backup_dir.relative_path(); | |
591 | path.push(&archive_name); | |
592 | ||
593 | let index = match env.datastore.open_fixed_reader(path) { | |
594 | Ok(index) => index, | |
595 | Err(_) => { | |
596 | env.log(format!("there is no last backup for archive '{}'", archive_name)); | |
597 | return Ok(Box::new(future::ok(empty_response))); | |
598 | } | |
599 | }; | |
600 | ||
39e60bd6 DM |
601 | env.log(format!("download last backup index for archive '{}'", archive_name)); |
602 | ||
a42fa400 DM |
603 | let count = index.index_count(); |
604 | for pos in 0..count { | |
605 | let digest = index.index_digest(pos).unwrap(); | |
606 | let size = index.chunk_size as u32; | |
607 | env.register_chunk(*digest, size)?; | |
608 | } | |
609 | ||
610 | let reader = DigestListEncoder::new(Box::new(index)); | |
611 | ||
612 | let stream = WrappedReaderStream::new(reader); | |
613 | ||
614 | // fixme: set size, content type? | |
615 | let response = http::Response::builder() | |
616 | .status(200) | |
617 | .body(Body::wrap_stream(stream))?; | |
618 | ||
619 | Ok(Box::new(future::ok(response))) | |
620 | } |