]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/proxmox-backup-proxy.rs
proxy: add scheduling for verification jobs
[proxmox-backup.git] / src / bin / proxmox-backup-proxy.rs
CommitLineData
c040ec22 1use std::sync::{Arc};
2ab5acac 2use std::path::{Path, PathBuf};
97168f92 3use std::os::unix::io::AsRawFd;
a2479cfa 4
f7d4e4b5 5use anyhow::{bail, format_err, Error};
a2479cfa
WB
6use futures::*;
7use hyper;
8use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
9
9ea4bce4 10use proxmox::try_block;
a2479cfa
WB
11use proxmox::api::RpcEnvironmentType;
12
e7cb4dc5 13use proxmox_backup::api2::types::Userid;
a2ca7137 14use proxmox_backup::configdir;
4a7de56e 15use proxmox_backup::buildcfg;
e3f41f21 16use proxmox_backup::server;
e57e1cd8 17use proxmox_backup::server::{ApiConfig, rest::*};
d01e2420 18use proxmox_backup::auth_helpers::*;
97168f92 19use proxmox_backup::tools::{
e4f5f59e 20 daemon,
97168f92
DM
21 disks::{
22 DiskManage,
23 zfs_pool_stats,
24 },
25 socket::{
26 set_tcp_keepalive,
27 PROXMOX_BACKUP_TCP_KEEPALIVE_TIME,
28 },
29};
02c7a755 30
a13573c2 31use proxmox_backup::api2::pull::do_sync_job;
73df9c51 32use proxmox_backup::backup::do_verification_job;
a13573c2 33
946c3e8a 34fn main() -> Result<(), Error> {
ac7513e3
DM
35 proxmox_backup::tools::setup_safe_path_env();
36
843880f0
TL
37 let backup_uid = proxmox_backup::backup::backup_user()?.uid;
38 let backup_gid = proxmox_backup::backup::backup_group()?.gid;
39 let running_uid = nix::unistd::Uid::effective();
40 let running_gid = nix::unistd::Gid::effective();
41
42 if running_uid != backup_uid || running_gid != backup_gid {
43 bail!("proxy not running as backup user or group (got uid {} gid {})", running_uid, running_gid);
44 }
45
946c3e8a 46 proxmox_backup::tools::runtime::main(run())
4223d9f8
DM
47}
48
fda5797b 49async fn run() -> Result<(), Error> {
02c7a755
DM
50 if let Err(err) = syslog::init(
51 syslog::Facility::LOG_DAEMON,
52 log::LevelFilter::Info,
53 Some("proxmox-backup-proxy")) {
4223d9f8 54 bail!("unable to inititialize syslog - {}", err);
02c7a755
DM
55 }
56
d01e2420
DM
57 let _ = public_auth_key(); // load with lazy_static
58 let _ = csrf_secret(); // load with lazy_static
59
02c7a755 60 let mut config = ApiConfig::new(
f9e3b110 61 buildcfg::JS_DIR, &proxmox_backup::api2::ROUTER, RpcEnvironmentType::PUBLIC)?;
02c7a755 62
02c7a755
DM
63 config.add_alias("novnc", "/usr/share/novnc-pve");
64 config.add_alias("extjs", "/usr/share/javascript/extjs");
65 config.add_alias("fontawesome", "/usr/share/fonts-font-awesome");
66 config.add_alias("xtermjs", "/usr/share/pve-xtermjs");
abd4c4cb 67 config.add_alias("locale", "/usr/share/pbs-i18n");
02c7a755 68 config.add_alias("widgettoolkit", "/usr/share/javascript/proxmox-widget-toolkit");
2d694f8f 69 config.add_alias("css", "/usr/share/javascript/proxmox-backup/css");
9c01e73c 70 config.add_alias("docs", "/usr/share/doc/proxmox-backup/html");
02c7a755 71
2ab5acac
DC
72 let mut indexpath = PathBuf::from(buildcfg::JS_DIR);
73 indexpath.push("index.hbs");
74 config.register_template("index", &indexpath)?;
01ca99da 75 config.register_template("console", "/usr/share/pve-xtermjs/index.html.hbs")?;
2ab5acac 76
8e7e2223
TL
77 config.enable_file_log(buildcfg::API_ACCESS_LOG_FN)?;
78
02c7a755
DM
79 let rest_server = RestServer::new(config);
80
6d1f61b2
DM
81 //openssl req -x509 -newkey rsa:4096 -keyout /etc/proxmox-backup/proxy.key -out /etc/proxmox-backup/proxy.pem -nodes
82 let key_path = configdir!("/proxy.key");
83 let cert_path = configdir!("/proxy.pem");
84
62c74d77 85 let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap();
6d1f61b2
DM
86 acceptor.set_private_key_file(key_path, SslFiletype::PEM)
87 .map_err(|err| format_err!("unable to read proxy key {} - {}", key_path, err))?;
88 acceptor.set_certificate_chain_file(cert_path)
89 .map_err(|err| format_err!("unable to read proxy cert {} - {}", cert_path, err))?;
90 acceptor.check_private_key().unwrap();
91
92 let acceptor = Arc::new(acceptor.build());
0d176f36 93
a690ecac
WB
94 let server = daemon::create_daemon(
95 ([0,0,0,0,0,0,0,0], 8007).into(),
083ff3fd 96 |listener, ready| {
db0cb9ce 97 let connections = proxmox_backup::tools::async_io::StaticIncoming::from(listener)
a690ecac 98 .map_err(Error::from)
db0cb9ce 99 .try_filter_map(move |(sock, _addr)| {
fda5797b
WB
100 let acceptor = Arc::clone(&acceptor);
101 async move {
102 sock.set_nodelay(true).unwrap();
97168f92
DM
103
104 let _ = set_tcp_keepalive(sock.as_raw_fd(), PROXMOX_BACKUP_TCP_KEEPALIVE_TIME);
105
fda5797b
WB
106 Ok(tokio_openssl::accept(&acceptor, sock)
107 .await
108 .ok() // handshake errors aren't be fatal, so return None to filter
109 )
a690ecac 110 }
a690ecac 111 });
db0cb9ce 112 let connections = proxmox_backup::tools::async_io::HyperAccept(connections);
083ff3fd
WB
113
114 Ok(ready
115 .and_then(|_| hyper::Server::builder(connections)
116 .serve(rest_server)
117 .with_graceful_shutdown(server::shutdown_future())
118 .map_err(Error::from)
119 )
120 .map_err(|err| eprintln!("server error: {}", err))
121 .map(|_| ())
a690ecac 122 )
a2ca7137 123 },
083ff3fd 124 );
a2ca7137 125
d98c9a7a
WB
126 daemon::systemd_notify(daemon::SystemdNotify::Ready)?;
127
fda5797b
WB
128 let init_result: Result<(), Error> = try_block!({
129 server::create_task_control_socket()?;
130 server::server_state_init()?;
131 Ok(())
132 });
d607b886 133
fda5797b
WB
134 if let Err(err) = init_result {
135 bail!("unable to start daemon - {}", err);
136 }
e3f41f21 137
8545480a 138 start_task_scheduler();
eaeda365 139 start_stat_generator();
8545480a 140
083ff3fd 141 server.await?;
a546a8a0
WB
142 log::info!("server shutting down, waiting for active workers to complete");
143 proxmox_backup::server::last_worker_future().await?;
fda5797b 144 log::info!("done - exit server");
e3f41f21 145
4223d9f8 146 Ok(())
02c7a755 147}
8545480a 148
eaeda365
DM
149fn start_stat_generator() {
150 let abort_future = server::shutdown_future();
151 let future = Box::pin(run_stat_generator());
152 let task = futures::future::select(future, abort_future);
153 tokio::spawn(task.map(|_| ()));
154}
155
8545480a
DM
156fn start_task_scheduler() {
157 let abort_future = server::shutdown_future();
158 let future = Box::pin(run_task_scheduler());
159 let task = futures::future::select(future, abort_future);
160 tokio::spawn(task.map(|_| ()));
161}
162
6a7be83e 163use std::time::{SystemTime, Instant, Duration, UNIX_EPOCH};
8545480a
DM
164
165fn next_minute() -> Result<Instant, Error> {
6a7be83e
DM
166 let now = SystemTime::now();
167 let epoch_now = now.duration_since(UNIX_EPOCH)?;
168 let epoch_next = Duration::from_secs((epoch_now.as_secs()/60 + 1)*60);
8545480a
DM
169 Ok(Instant::now() + epoch_next - epoch_now)
170}
171
172async fn run_task_scheduler() {
173
174 let mut count: usize = 0;
175
176 loop {
177 count += 1;
178
179 let delay_target = match next_minute() { // try to run very minute
180 Ok(d) => d,
181 Err(err) => {
182 eprintln!("task scheduler: compute next minute failed - {}", err);
183 tokio::time::delay_until(tokio::time::Instant::from_std(Instant::now() + Duration::from_secs(60))).await;
184 continue;
185 }
186 };
187
188 if count > 2 { // wait 1..2 minutes before starting
189 match schedule_tasks().catch_unwind().await {
190 Err(panic) => {
191 match panic.downcast::<&str>() {
192 Ok(msg) => {
193 eprintln!("task scheduler panic: {}", msg);
194 }
195 Err(_) => {
196 eprintln!("task scheduler panic - unknown type");
197 }
198 }
199 }
200 Ok(Err(err)) => {
201 eprintln!("task scheduler failed - {:?}", err);
202 }
203 Ok(Ok(_)) => {}
204 }
205 }
206
207 tokio::time::delay_until(tokio::time::Instant::from_std(delay_target)).await;
208 }
209}
210
211async fn schedule_tasks() -> Result<(), Error> {
212
213 schedule_datastore_garbage_collection().await;
25829a87 214 schedule_datastore_prune().await;
c040ec22 215 schedule_datastore_verification().await;
a6160cdf 216 schedule_datastore_sync_jobs().await;
73df9c51 217 schedule_datastore_verify_jobs().await;
9a760917 218 schedule_task_log_rotate().await;
8545480a
DM
219
220 Ok(())
221}
222
8545480a
DM
223async fn schedule_datastore_garbage_collection() {
224
225 use proxmox_backup::backup::DataStore;
226 use proxmox_backup::server::{UPID, WorkerTask};
d7a122a0
DC
227 use proxmox_backup::config::{
228 jobstate::{self, Job},
229 datastore::{self, DataStoreConfig}
230 };
8545480a
DM
231 use proxmox_backup::tools::systemd::time::{
232 parse_calendar_event, compute_next_event};
233
25829a87 234 let config = match datastore::config() {
8545480a
DM
235 Err(err) => {
236 eprintln!("unable to read datastore config - {}", err);
237 return;
238 }
239 Ok((config, _digest)) => config,
240 };
241
242 for (store, (_, store_config)) in config.sections {
243 let datastore = match DataStore::lookup_datastore(&store) {
244 Ok(datastore) => datastore,
245 Err(err) => {
246 eprintln!("lookup_datastore failed - {}", err);
247 continue;
248 }
249 };
250
25829a87 251 let store_config: DataStoreConfig = match serde_json::from_value(store_config) {
8545480a
DM
252 Ok(c) => c,
253 Err(err) => {
254 eprintln!("datastore config from_value failed - {}", err);
255 continue;
256 }
257 };
258
259 let event_str = match store_config.gc_schedule {
260 Some(event_str) => event_str,
261 None => continue,
262 };
263
264 let event = match parse_calendar_event(&event_str) {
265 Ok(event) => event,
266 Err(err) => {
267 eprintln!("unable to parse schedule '{}' - {}", event_str, err);
268 continue;
269 }
270 };
271
272 if datastore.garbage_collection_running() { continue; }
273
274 let worker_type = "garbage_collection";
275
276 let stat = datastore.last_gc_status();
277 let last = if let Some(upid_str) = stat.upid {
278 match upid_str.parse::<UPID>() {
279 Ok(upid) => upid.starttime,
280 Err(err) => {
281 eprintln!("unable to parse upid '{}' - {}", upid_str, err);
282 continue;
283 }
284 }
285 } else {
d7a122a0
DC
286 match jobstate::last_run_time(worker_type, &store) {
287 Ok(time) => time,
8545480a 288 Err(err) => {
d7a122a0 289 eprintln!("could not get last run time of {} {}: {}", worker_type, store, err);
8545480a
DM
290 continue;
291 }
292 }
293 };
294
295 let next = match compute_next_event(&event, last, false) {
15ec790a
DC
296 Ok(Some(next)) => next,
297 Ok(None) => continue,
8545480a
DM
298 Err(err) => {
299 eprintln!("compute_next_event for '{}' failed - {}", event_str, err);
300 continue;
301 }
302 };
e693818a 303
6a7be83e
DM
304 let now = proxmox::tools::time::epoch_i64();
305
8545480a
DM
306 if next > now { continue; }
307
d7a122a0
DC
308 let mut job = match Job::new(worker_type, &store) {
309 Ok(job) => job,
310 Err(_) => continue, // could not get lock
311 };
312
8545480a
DM
313 let store2 = store.clone();
314
315 if let Err(err) = WorkerTask::new_thread(
316 worker_type,
317 Some(store.clone()),
e7cb4dc5 318 Userid::backup_userid().clone(),
8545480a
DM
319 false,
320 move |worker| {
d7a122a0
DC
321 job.start(&worker.upid().to_string())?;
322
8545480a
DM
323 worker.log(format!("starting garbage collection on store {}", store));
324 worker.log(format!("task triggered by schedule '{}'", event_str));
d7a122a0 325
f6b1d1cc 326 let result = datastore.garbage_collection(&*worker, worker.upid());
d7a122a0
DC
327
328 let status = worker.create_state(&result);
329
330 if let Err(err) = job.finish(status) {
331 eprintln!("could not finish job state for {}: {}", worker_type, err);
332 }
333
334 result
8545480a
DM
335 }
336 ) {
337 eprintln!("unable to start garbage collection on store {} - {}", store2, err);
338 }
339 }
340}
25829a87
DM
341
342async fn schedule_datastore_prune() {
343
344 use proxmox_backup::backup::{
6a7be83e 345 PruneOptions, DataStore, BackupGroup, compute_prune_info};
25829a87 346 use proxmox_backup::server::{WorkerTask};
9866de5e
DC
347 use proxmox_backup::config::{
348 jobstate::{self, Job},
349 datastore::{self, DataStoreConfig}
350 };
25829a87
DM
351 use proxmox_backup::tools::systemd::time::{
352 parse_calendar_event, compute_next_event};
353
354 let config = match datastore::config() {
355 Err(err) => {
356 eprintln!("unable to read datastore config - {}", err);
357 return;
358 }
359 Ok((config, _digest)) => config,
360 };
361
362 for (store, (_, store_config)) in config.sections {
363 let datastore = match DataStore::lookup_datastore(&store) {
364 Ok(datastore) => datastore,
365 Err(err) => {
a6160cdf 366 eprintln!("lookup_datastore '{}' failed - {}", store, err);
25829a87
DM
367 continue;
368 }
369 };
370
371 let store_config: DataStoreConfig = match serde_json::from_value(store_config) {
372 Ok(c) => c,
373 Err(err) => {
a6160cdf 374 eprintln!("datastore '{}' config from_value failed - {}", store, err);
25829a87
DM
375 continue;
376 }
377 };
378
379 let event_str = match store_config.prune_schedule {
380 Some(event_str) => event_str,
381 None => continue,
382 };
383
384 let prune_options = PruneOptions {
385 keep_last: store_config.keep_last,
386 keep_hourly: store_config.keep_hourly,
387 keep_daily: store_config.keep_daily,
388 keep_weekly: store_config.keep_weekly,
389 keep_monthly: store_config.keep_monthly,
390 keep_yearly: store_config.keep_yearly,
391 };
392
393 if !prune_options.keeps_something() { // no prune settings - keep all
394 continue;
395 }
396
397 let event = match parse_calendar_event(&event_str) {
398 Ok(event) => event,
399 Err(err) => {
400 eprintln!("unable to parse schedule '{}' - {}", event_str, err);
401 continue;
402 }
403 };
404
25829a87
DM
405 let worker_type = "prune";
406
9866de5e
DC
407 let last = match jobstate::last_run_time(worker_type, &store) {
408 Ok(time) => time,
25829a87 409 Err(err) => {
9866de5e 410 eprintln!("could not get last run time of {} {}: {}", worker_type, store, err);
25829a87
DM
411 continue;
412 }
413 };
414
415 let next = match compute_next_event(&event, last, false) {
15ec790a
DC
416 Ok(Some(next)) => next,
417 Ok(None) => continue,
25829a87
DM
418 Err(err) => {
419 eprintln!("compute_next_event for '{}' failed - {}", event_str, err);
420 continue;
421 }
422 };
423
6a7be83e
DM
424 let now = proxmox::tools::time::epoch_i64();
425
25829a87
DM
426 if next > now { continue; }
427
9866de5e
DC
428 let mut job = match Job::new(worker_type, &store) {
429 Ok(job) => job,
430 Err(_) => continue, // could not get lock
431 };
432
25829a87
DM
433 let store2 = store.clone();
434
435 if let Err(err) = WorkerTask::new_thread(
436 worker_type,
437 Some(store.clone()),
e7cb4dc5 438 Userid::backup_userid().clone(),
25829a87
DM
439 false,
440 move |worker| {
9866de5e
DC
441
442 job.start(&worker.upid().to_string())?;
443
6c25588e 444 let result = try_block!({
9866de5e
DC
445
446 worker.log(format!("Starting datastore prune on store \"{}\"", store));
447 worker.log(format!("task triggered by schedule '{}'", event_str));
448 worker.log(format!("retention options: {}", prune_options.cli_options_string()));
449
450 let base_path = datastore.base_path();
451
452 let groups = BackupGroup::list_groups(&base_path)?;
453 for group in groups {
454 let list = group.list_backups(&base_path)?;
455 let mut prune_info = compute_prune_info(list, &prune_options)?;
456 prune_info.reverse(); // delete older snapshots first
457
458 worker.log(format!("Starting prune on store \"{}\" group \"{}/{}\"",
459 store, group.backup_type(), group.backup_id()));
460
461 for (info, keep) in prune_info {
462 worker.log(format!(
463 "{} {}/{}/{}",
464 if keep { "keep" } else { "remove" },
465 group.backup_type(), group.backup_id(),
466 info.backup_dir.backup_time_string()));
467 if !keep {
468 datastore.remove_backup_dir(&info.backup_dir, true)?;
469 }
25829a87
DM
470 }
471 }
9866de5e 472 Ok(())
6c25588e 473 });
9866de5e
DC
474
475 let status = worker.create_state(&result);
476
477 if let Err(err) = job.finish(status) {
478 eprintln!("could not finish job state for {}: {}", worker_type, err);
25829a87
DM
479 }
480
9866de5e 481 result
25829a87
DM
482 }
483 ) {
484 eprintln!("unable to start datastore prune on store {} - {}", store2, err);
485 }
486 }
487}
a6160cdf 488
c040ec22
HL
489async fn schedule_datastore_verification() {
490 use proxmox_backup::backup::{DataStore, verify_all_backups};
491 use proxmox_backup::server::{WorkerTask};
d7a122a0
DC
492 use proxmox_backup::config::{
493 jobstate::{self, Job},
494 datastore::{self, DataStoreConfig}
495 };
c040ec22
HL
496 use proxmox_backup::tools::systemd::time::{
497 parse_calendar_event, compute_next_event};
498
499 let config = match datastore::config() {
500 Err(err) => {
501 eprintln!("unable to read datastore config - {}", err);
502 return;
503 }
504 Ok((config, _digest)) => config,
505 };
506
507 for (store, (_, store_config)) in config.sections {
508 let datastore = match DataStore::lookup_datastore(&store) {
509 Ok(datastore) => datastore,
510 Err(err) => {
511 eprintln!("lookup_datastore failed - {}", err);
512 continue;
513 }
514 };
515
516 let store_config: DataStoreConfig = match serde_json::from_value(store_config) {
517 Ok(c) => c,
518 Err(err) => {
519 eprintln!("datastore config from_value failed - {}", err);
520 continue;
521 }
522 };
523
524 let event_str = match store_config.verify_schedule {
525 Some(event_str) => event_str,
526 None => continue,
527 };
528
529 let event = match parse_calendar_event(&event_str) {
530 Ok(event) => event,
531 Err(err) => {
532 eprintln!("unable to parse schedule '{}' - {}", event_str, err);
533 continue;
534 }
535 };
536
537 let worker_type = "verify";
538
d7a122a0
DC
539 let last = match jobstate::last_run_time(worker_type, &store) {
540 Ok(time) => time,
c040ec22 541 Err(err) => {
d7a122a0 542 eprintln!("could not get last run time of {} {}: {}", worker_type, store, err);
c040ec22
HL
543 continue;
544 }
545 };
546
547 let next = match compute_next_event(&event, last, false) {
548 Ok(Some(next)) => next,
549 Ok(None) => continue,
550 Err(err) => {
551 eprintln!("compute_next_event for '{}' failed - {}", event_str, err);
552 continue;
553 }
554 };
555
556 let now = proxmox::tools::time::epoch_i64();
557
558 if next > now { continue; }
559
d7a122a0
DC
560 let mut job = match Job::new(worker_type, &store) {
561 Ok(job) => job,
562 Err(_) => continue, // could not get lock
563 };
564
c040ec22
HL
565 let worker_id = store.clone();
566 let store2 = store.clone();
567 if let Err(err) = WorkerTask::new_thread(
568 worker_type,
569 Some(worker_id),
570 Userid::backup_userid().clone(),
571 false,
572 move |worker| {
d7a122a0 573 job.start(&worker.upid().to_string())?;
c040ec22
HL
574 worker.log(format!("starting verification on store {}", store2));
575 worker.log(format!("task triggered by schedule '{}'", event_str));
d7a122a0 576 let result = try_block!({
f6b1d1cc
WB
577 let failed_dirs =
578 verify_all_backups(datastore, worker.clone(), worker.upid())?;
c040ec22
HL
579 if failed_dirs.len() > 0 {
580 worker.log("Failed to verify following snapshots:");
581 for dir in failed_dirs {
582 worker.log(format!("\t{}", dir));
583 }
d7a122a0
DC
584 Err(format_err!("verification failed - please check the log for details"))
585 } else {
586 Ok(())
c040ec22 587 }
d7a122a0
DC
588 });
589
590 let status = worker.create_state(&result);
591
592 if let Err(err) = job.finish(status) {
593 eprintln!("could not finish job state for {}: {}", worker_type, err);
c040ec22 594 }
d7a122a0
DC
595
596 result
c040ec22
HL
597 },
598 ) {
599 eprintln!("unable to start verification on store {} - {}", store, err);
600 }
601 }
602}
603
a6160cdf
DM
604async fn schedule_datastore_sync_jobs() {
605
606 use proxmox_backup::{
a13573c2 607 config::{ sync::{self, SyncJobConfig}, jobstate::{self, Job} },
a6160cdf
DM
608 tools::systemd::time::{ parse_calendar_event, compute_next_event },
609 };
610
611 let config = match sync::config() {
612 Err(err) => {
613 eprintln!("unable to read sync job config - {}", err);
614 return;
615 }
616 Ok((config, _digest)) => config,
617 };
618
a6160cdf
DM
619 for (job_id, (_, job_config)) in config.sections {
620 let job_config: SyncJobConfig = match serde_json::from_value(job_config) {
621 Ok(c) => c,
622 Err(err) => {
623 eprintln!("sync job config from_value failed - {}", err);
624 continue;
625 }
626 };
627
628 let event_str = match job_config.schedule {
629 Some(ref event_str) => event_str.clone(),
630 None => continue,
631 };
632
633 let event = match parse_calendar_event(&event_str) {
634 Ok(event) => event,
635 Err(err) => {
636 eprintln!("unable to parse schedule '{}' - {}", event_str, err);
637 continue;
638 }
639 };
640
c67b1fa7 641 let worker_type = "syncjob";
a6160cdf 642
a13573c2
DC
643 let last = match jobstate::last_run_time(worker_type, &job_id) {
644 Ok(time) => time,
a6160cdf 645 Err(err) => {
a13573c2 646 eprintln!("could not get last run time of {} {}: {}", worker_type, job_id, err);
a6160cdf
DM
647 continue;
648 }
649 };
650
651 let next = match compute_next_event(&event, last, false) {
15ec790a
DC
652 Ok(Some(next)) => next,
653 Ok(None) => continue,
a6160cdf
DM
654 Err(err) => {
655 eprintln!("compute_next_event for '{}' failed - {}", event_str, err);
656 continue;
657 }
658 };
659
6a7be83e
DM
660 let now = proxmox::tools::time::epoch_i64();
661
a6160cdf
DM
662 if next > now { continue; }
663
a13573c2 664 let job = match Job::new(worker_type, &job_id) {
93bb51fe 665 Ok(job) => job,
a13573c2 666 Err(_) => continue, // could not get lock
a6160cdf
DM
667 };
668
e7cb4dc5 669 let userid = Userid::backup_userid().clone();
a6160cdf 670
713b66b6 671 if let Err(err) = do_sync_job(job, job_config, &userid, Some(event_str)) {
a13573c2 672 eprintln!("unable to start datastore sync job {} - {}", &job_id, err);
a6160cdf
DM
673 }
674 }
675}
eaeda365 676
73df9c51
HL
677async fn schedule_datastore_verify_jobs() {
678 use proxmox_backup::{
679 config::{verify::{self, VerificationJobConfig}, jobstate::{self, Job}},
680 tools::systemd::time::{parse_calendar_event, compute_next_event},
681 };
682 let config = match verify::config() {
683 Err(err) => {
684 eprintln!("unable to read verification job config - {}", err);
685 return;
686 }
687 Ok((config, _digest)) => config,
688 };
689 for (job_id, (_, job_config)) in config.sections {
690 let job_config: VerificationJobConfig = match serde_json::from_value(job_config) {
691 Ok(c) => c,
692 Err(err) => {
693 eprintln!("verification job config from_value failed - {}", err);
694 continue;
695 }
696 };
697 let event_str = match job_config.schedule {
698 Some(ref event_str) => event_str.clone(),
699 None => continue,
700 };
701 let event = match parse_calendar_event(&event_str) {
702 Ok(event) => event,
703 Err(err) => {
704 eprintln!("unable to parse schedule '{}' - {}", event_str, err);
705 continue;
706 }
707 };
708 let worker_type = "verificationjob";
709 let last = match jobstate::last_run_time(worker_type, &job_id) {
710 Ok(time) => time,
711 Err(err) => {
712 eprintln!("could not get last run time of {} {}: {}", worker_type, job_id, err);
713 continue;
714 }
715 };
716 let next = match compute_next_event(&event, last, false) {
717 Ok(Some(next)) => next,
718 Ok(None) => continue,
719 Err(err) => {
720 eprintln!("compute_next_event for '{}' failed - {}", event_str, err);
721 continue;
722 }
723 };
724 let now = proxmox::tools::time::epoch_i64();
725 if next > now { continue; }
726 let job = match Job::new(worker_type, &job_id) {
727 Ok(job) => job,
728 Err(_) => continue, // could not get lock
729 };
730 let userid = Userid::backup_userid().clone();
731 if let Err(err) = do_verification_job(job, job_config, &userid, Some(event_str)) {
732 eprintln!("unable to start datastore verification job {} - {}", &job_id, err);
733 }
734 }
735}
736
9a760917
DC
737async fn schedule_task_log_rotate() {
738 use proxmox_backup::{
739 config::jobstate::{self, Job},
740 server::rotate_task_log_archive,
741 };
742 use proxmox_backup::server::WorkerTask;
743 use proxmox_backup::tools::systemd::time::{
744 parse_calendar_event, compute_next_event};
745
746 let worker_type = "logrotate";
747 let job_id = "task-archive";
748
749 let last = match jobstate::last_run_time(worker_type, job_id) {
750 Ok(time) => time,
751 Err(err) => {
752 eprintln!("could not get last run time of task log archive rotation: {}", err);
753 return;
754 }
755 };
756
757 // schedule daily at 00:00 like normal logrotate
758 let schedule = "00:00";
759
760 let event = match parse_calendar_event(schedule) {
761 Ok(event) => event,
762 Err(err) => {
763 // should not happen?
764 eprintln!("unable to parse schedule '{}' - {}", schedule, err);
765 return;
766 }
767 };
768
769 let next = match compute_next_event(&event, last, false) {
770 Ok(Some(next)) => next,
771 Ok(None) => return,
772 Err(err) => {
773 eprintln!("compute_next_event for '{}' failed - {}", schedule, err);
774 return;
775 }
776 };
777
778 let now = proxmox::tools::time::epoch_i64();
779
780 if next > now {
781 // if we never ran the rotation, schedule instantly
782 match jobstate::JobState::load(worker_type, job_id) {
783 Ok(state) => match state {
784 jobstate::JobState::Created { .. } => {},
785 _ => return,
786 },
787 _ => return,
788 }
789 }
790
791 let mut job = match Job::new(worker_type, job_id) {
792 Ok(job) => job,
793 Err(_) => return, // could not get lock
794 };
795
796 if let Err(err) = WorkerTask::new_thread(
797 worker_type,
798 Some(job_id.to_string()),
799 Userid::backup_userid().clone(),
800 false,
801 move |worker| {
802 job.start(&worker.upid().to_string())?;
803 worker.log(format!("starting task log rotation"));
e4f5f59e 804
9a760917 805 let result = try_block!({
e4f5f59e
TL
806 // rotate task log archive
807 let max_size = 500000; // a normal entry has about 100b, so ~ 5000 entries/file
808 let max_files = 20; // times twenty files gives at least 100000 task entries
9a760917
DC
809 let has_rotated = rotate_task_log_archive(max_size, true, Some(max_files))?;
810 if has_rotated {
811 worker.log(format!("task log archive was rotated"));
812 } else {
813 worker.log(format!("task log archive was not rotated"));
814 }
815
816 Ok(())
817 });
818
819 let status = worker.create_state(&result);
820
821 if let Err(err) = job.finish(status) {
822 eprintln!("could not finish job state for {}: {}", worker_type, err);
823 }
824
825 result
826 },
827 ) {
828 eprintln!("unable to start task log rotation: {}", err);
829 }
830
831}
832
eaeda365
DM
833async fn run_stat_generator() {
834
013fa7bb 835 let mut count = 0;
eaeda365 836 loop {
013fa7bb 837 count += 1;
a720894f 838 let save = if count >= 6 { count = 0; true } else { false };
013fa7bb 839
eaeda365
DM
840 let delay_target = Instant::now() + Duration::from_secs(10);
841
013fa7bb 842 generate_host_stats(save).await;
eaeda365
DM
843
844 tokio::time::delay_until(tokio::time::Instant::from_std(delay_target)).await;
013fa7bb
DM
845
846 }
eaeda365
DM
847
848}
849
013fa7bb 850fn rrd_update_gauge(name: &str, value: f64, save: bool) {
309ef20d 851 use proxmox_backup::rrd;
013fa7bb 852 if let Err(err) = rrd::update_value(name, value, rrd::DST::Gauge, save) {
309ef20d
DM
853 eprintln!("rrd::update_value '{}' failed - {}", name, err);
854 }
855}
856
013fa7bb 857fn rrd_update_derive(name: &str, value: f64, save: bool) {
309ef20d 858 use proxmox_backup::rrd;
013fa7bb 859 if let Err(err) = rrd::update_value(name, value, rrd::DST::Derive, save) {
309ef20d
DM
860 eprintln!("rrd::update_value '{}' failed - {}", name, err);
861 }
862}
863
013fa7bb 864async fn generate_host_stats(save: bool) {
8f0cec26 865 use proxmox::sys::linux::procfs::{
485841da 866 read_meminfo, read_proc_stat, read_proc_net_dev, read_loadavg};
309ef20d 867 use proxmox_backup::config::datastore;
8c03041a 868
eaeda365 869
4f951399
DM
870 proxmox_backup::tools::runtime::block_in_place(move || {
871
872 match read_proc_stat() {
873 Ok(stat) => {
013fa7bb
DM
874 rrd_update_gauge("host/cpu", stat.cpu, save);
875 rrd_update_gauge("host/iowait", stat.iowait_percent, save);
4f951399
DM
876 }
877 Err(err) => {
878 eprintln!("read_proc_stat failed - {}", err);
eaeda365
DM
879 }
880 }
2c66a590 881
4f951399
DM
882 match read_meminfo() {
883 Ok(meminfo) => {
013fa7bb
DM
884 rrd_update_gauge("host/memtotal", meminfo.memtotal as f64, save);
885 rrd_update_gauge("host/memused", meminfo.memused as f64, save);
886 rrd_update_gauge("host/swaptotal", meminfo.swaptotal as f64, save);
887 rrd_update_gauge("host/swapused", meminfo.swapused as f64, save);
a4a3f7ca 888 }
4f951399
DM
889 Err(err) => {
890 eprintln!("read_meminfo failed - {}", err);
a4a3f7ca
DM
891 }
892 }
8f0cec26 893
4f951399
DM
894 match read_proc_net_dev() {
895 Ok(netdev) => {
896 use proxmox_backup::config::network::is_physical_nic;
897 let mut netin = 0;
898 let mut netout = 0;
899 for item in netdev {
900 if !is_physical_nic(&item.device) { continue; }
901 netin += item.receive;
902 netout += item.send;
903 }
013fa7bb
DM
904 rrd_update_derive("host/netin", netin as f64, save);
905 rrd_update_derive("host/netout", netout as f64, save);
8f0cec26 906 }
4f951399
DM
907 Err(err) => {
908 eprintln!("read_prox_net_dev failed - {}", err);
8f0cec26
DM
909 }
910 }
dd15c0aa 911
485841da
DM
912 match read_loadavg() {
913 Ok(loadavg) => {
013fa7bb 914 rrd_update_gauge("host/loadavg", loadavg.0 as f64, save);
485841da
DM
915 }
916 Err(err) => {
917 eprintln!("read_loadavg failed - {}", err);
918 }
919 }
920
8c03041a
DM
921 let disk_manager = DiskManage::new();
922
013fa7bb 923 gather_disk_stats(disk_manager.clone(), Path::new("/"), "host", save);
91e5bb49 924
d0833a70
DM
925 match datastore::config() {
926 Ok((config, _)) => {
927 let datastore_list: Vec<datastore::DataStoreConfig> =
928 config.convert_to_typed_array("datastore").unwrap_or(Vec::new());
929
930 for config in datastore_list {
8c03041a 931
91e5bb49 932 let rrd_prefix = format!("datastore/{}", config.name);
8c03041a 933 let path = std::path::Path::new(&config.path);
013fa7bb 934 gather_disk_stats(disk_manager.clone(), path, &rrd_prefix, save);
d0833a70
DM
935 }
936 }
937 Err(err) => {
938 eprintln!("read datastore config failed - {}", err);
939 }
940 }
941
4f951399 942 });
eaeda365 943}
dd15c0aa 944
013fa7bb 945fn gather_disk_stats(disk_manager: Arc<DiskManage>, path: &Path, rrd_prefix: &str, save: bool) {
91e5bb49 946
934f5bb8 947 match proxmox_backup::tools::disks::disk_usage(path) {
33070956 948 Ok(status) => {
91e5bb49 949 let rrd_key = format!("{}/total", rrd_prefix);
33070956 950 rrd_update_gauge(&rrd_key, status.total as f64, save);
91e5bb49 951 let rrd_key = format!("{}/used", rrd_prefix);
33070956 952 rrd_update_gauge(&rrd_key, status.used as f64, save);
91e5bb49
DM
953 }
954 Err(err) => {
955 eprintln!("read disk_usage on {:?} failed - {}", path, err);
956 }
957 }
958
934f5bb8
DM
959 match disk_manager.find_mounted_device(path) {
960 Ok(None) => {},
961 Ok(Some((fs_type, device, source))) => {
962 let mut device_stat = None;
963 match fs_type.as_str() {
964 "zfs" => {
965 if let Some(pool) = source {
966 match zfs_pool_stats(&pool) {
967 Ok(stat) => device_stat = stat,
968 Err(err) => eprintln!("zfs_pool_stats({:?}) failed - {}", pool, err),
91e5bb49
DM
969 }
970 }
934f5bb8
DM
971 }
972 _ => {
973 if let Ok(disk) = disk_manager.clone().disk_by_dev_num(device.into_dev_t()) {
974 match disk.read_stat() {
975 Ok(stat) => device_stat = stat,
976 Err(err) => eprintln!("disk.read_stat {:?} failed - {}", path, err),
91e5bb49
DM
977 }
978 }
979 }
91e5bb49 980 }
934f5bb8
DM
981 if let Some(stat) = device_stat {
982 let rrd_key = format!("{}/read_ios", rrd_prefix);
983 rrd_update_derive(&rrd_key, stat.read_ios as f64, save);
984 let rrd_key = format!("{}/read_bytes", rrd_prefix);
985 rrd_update_derive(&rrd_key, (stat.read_sectors*512) as f64, save);
dd15c0aa 986
934f5bb8
DM
987 let rrd_key = format!("{}/write_ios", rrd_prefix);
988 rrd_update_derive(&rrd_key, stat.write_ios as f64, save);
989 let rrd_key = format!("{}/write_bytes", rrd_prefix);
990 rrd_update_derive(&rrd_key, (stat.write_sectors*512) as f64, save);
dd15c0aa 991
934f5bb8
DM
992 let rrd_key = format!("{}/io_ticks", rrd_prefix);
993 rrd_update_derive(&rrd_key, (stat.io_ticks as f64)/1000.0, save);
8c03041a
DM
994 }
995 }
934f5bb8
DM
996 Err(err) => {
997 eprintln!("find_mounted_device failed - {}", err);
998 }
8c03041a 999 }
8c03041a 1000}