send notification mails for GC and verify jobs
This commit is contained in:
parent
acc3d9df5a
commit
b9e7bcc272
@ -240,11 +240,19 @@ async fn schedule_tasks() -> Result<(), Error> {
|
||||
|
||||
async fn schedule_datastore_garbage_collection() {
|
||||
|
||||
use proxmox_backup::config::datastore::{
|
||||
self,
|
||||
DataStoreConfig,
|
||||
use proxmox_backup::config::{
|
||||
datastore::{
|
||||
self,
|
||||
DataStoreConfig,
|
||||
},
|
||||
user::{
|
||||
self,
|
||||
User,
|
||||
},
|
||||
};
|
||||
|
||||
let email = server::lookup_user_email(Userid::root_userid());
|
||||
|
||||
let config = match datastore::config() {
|
||||
Err(err) => {
|
||||
eprintln!("unable to read datastore config - {}", err);
|
||||
@ -325,6 +333,7 @@ async fn schedule_datastore_garbage_collection() {
|
||||
};
|
||||
|
||||
let store2 = store.clone();
|
||||
let email2 = email.clone();
|
||||
|
||||
if let Err(err) = WorkerTask::new_thread(
|
||||
worker_type,
|
||||
@ -345,6 +354,13 @@ async fn schedule_datastore_garbage_collection() {
|
||||
eprintln!("could not finish job state for {}: {}", worker_type, err);
|
||||
}
|
||||
|
||||
if let Some(email2) = email2 {
|
||||
let gc_status = datastore.last_gc_status();
|
||||
if let Err(err) = crate::server::send_gc_status(&email2, datastore.name(), &gc_status, &result) {
|
||||
eprintln!("send gc notification failed: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
) {
|
||||
|
@ -34,3 +34,6 @@ pub mod jobstate;
|
||||
|
||||
mod verify_job;
|
||||
pub use verify_job::*;
|
||||
|
||||
mod email_notifications;
|
||||
pub use email_notifications::*;
|
||||
|
242
src/server/email_notifications.rs
Normal file
242
src/server/email_notifications.rs
Normal file
@ -0,0 +1,242 @@
|
||||
use anyhow::Error;
|
||||
use serde_json::json;
|
||||
|
||||
use handlebars::{Handlebars, Helper, Context, RenderError, RenderContext, Output, HelperResult};
|
||||
|
||||
use proxmox::tools::email::sendmail;
|
||||
|
||||
use crate::{
|
||||
config::verify::VerificationJobConfig,
|
||||
api2::types::{
|
||||
Userid,
|
||||
GarbageCollectionStatus,
|
||||
},
|
||||
tools::format::HumanByte,
|
||||
};
|
||||
|
||||
const GC_OK_TEMPLATE: &str = r###"
|
||||
|
||||
Datastore: {{datastore}}
|
||||
Task ID: {{status.upid}}
|
||||
Index file count: {{status.index-file-count}}
|
||||
|
||||
Removed garbage: {{human-bytes status.removed-bytes}}
|
||||
Removed chunks: {{status.removed-chunks}}
|
||||
Remove bad files: {{status.removed-bad}}
|
||||
|
||||
Pending removals: {{human-bytes status.pending-bytes}} (in {{status.pending-chunks}} chunks)
|
||||
|
||||
Original Data usage: {{human-bytes status.index-data-bytes}}
|
||||
On Disk usage: {{human-bytes status.disk-bytes}} ({{relative-percentage status.disk-bytes status.index-data-bytes}})
|
||||
On Disk chunks: {{status.disk-chunks}}
|
||||
|
||||
Garbage collection successful.
|
||||
|
||||
"###;
|
||||
|
||||
|
||||
const GC_ERR_TEMPLATE: &str = r###"
|
||||
|
||||
Datastore: {{datastore}}
|
||||
|
||||
Garbage collection failed: {{error}}
|
||||
|
||||
"###;
|
||||
|
||||
const VERIFY_OK_TEMPLATE: &str = r###"
|
||||
|
||||
Job ID: {{job.id}}
|
||||
Datastore: {{job.store}}
|
||||
|
||||
Verification successful.
|
||||
|
||||
"###;
|
||||
|
||||
const VERIFY_ERR_TEMPLATE: &str = r###"
|
||||
|
||||
Job ID: {{job.id}}
|
||||
Datastore: {{job.store}}
|
||||
|
||||
Verification failed: {{error}}
|
||||
|
||||
"###;
|
||||
|
||||
lazy_static::lazy_static!{
|
||||
|
||||
static ref HANDLEBARS: Handlebars<'static> = {
|
||||
let mut hb = Handlebars::new();
|
||||
|
||||
hb.set_strict_mode(true);
|
||||
|
||||
hb.register_helper("human-bytes", Box::new(handlebars_humam_bytes_helper));
|
||||
hb.register_helper("relative-percentage", Box::new(handlebars_relative_percentage_helper));
|
||||
|
||||
hb.register_template_string("gc_ok_template", GC_OK_TEMPLATE).unwrap();
|
||||
hb.register_template_string("gc_err_template", GC_ERR_TEMPLATE).unwrap();
|
||||
|
||||
hb.register_template_string("verify_ok_template", VERIFY_OK_TEMPLATE).unwrap();
|
||||
hb.register_template_string("verify_err_template", VERIFY_ERR_TEMPLATE).unwrap();
|
||||
|
||||
hb
|
||||
};
|
||||
}
|
||||
|
||||
fn send_job_status_mail(
|
||||
email: &str,
|
||||
subject: &str,
|
||||
text: &str,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
// Note: OX has serious problems displaying text mails,
|
||||
// so we include html as well
|
||||
let html = format!("<html><body><pre>\n{}\n<pre>", text);
|
||||
|
||||
let nodename = proxmox::tools::nodename();
|
||||
|
||||
let author = format!("Proxmox Backup Server - {}", nodename);
|
||||
|
||||
sendmail(
|
||||
&[email],
|
||||
&subject,
|
||||
Some(&text),
|
||||
Some(&html),
|
||||
None,
|
||||
Some(&author),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_gc_status(
|
||||
email: &str,
|
||||
datastore: &str,
|
||||
status: &GarbageCollectionStatus,
|
||||
result: &Result<(), Error>,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
let text = match result {
|
||||
Ok(()) => {
|
||||
let data = json!({
|
||||
"status": status,
|
||||
"datastore": datastore,
|
||||
});
|
||||
HANDLEBARS.render("gc_ok_template", &data)?
|
||||
}
|
||||
Err(err) => {
|
||||
let data = json!({
|
||||
"error": err.to_string(),
|
||||
"datastore": datastore,
|
||||
});
|
||||
HANDLEBARS.render("gc_err_template", &data)?
|
||||
}
|
||||
};
|
||||
|
||||
let subject = match result {
|
||||
Ok(()) => format!(
|
||||
"Garbage Collect Datastore '{}' successful",
|
||||
datastore,
|
||||
),
|
||||
Err(_) => format!(
|
||||
"Garbage Collect Datastore '{}' failed",
|
||||
datastore,
|
||||
),
|
||||
};
|
||||
|
||||
send_job_status_mail(email, &subject, &text)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_verify_status(
|
||||
email: &str,
|
||||
job: VerificationJobConfig,
|
||||
result: &Result<(), Error>,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
|
||||
let text = match result {
|
||||
Ok(()) => {
|
||||
let data = json!({ "job": job });
|
||||
HANDLEBARS.render("verify_ok_template", &data)?
|
||||
}
|
||||
Err(err) => {
|
||||
let data = json!({ "job": job, "error": err.to_string() });
|
||||
HANDLEBARS.render("verify_err_template", &data)?
|
||||
}
|
||||
};
|
||||
|
||||
let subject = match result {
|
||||
Ok(()) => format!(
|
||||
"Verify Datastore '{}' successful",
|
||||
job.store,
|
||||
),
|
||||
Err(_) => format!(
|
||||
"Verify Datastore '{}' failed",
|
||||
job.store,
|
||||
),
|
||||
};
|
||||
|
||||
send_job_status_mail(email, &subject, &text)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lookup users email address
|
||||
///
|
||||
/// For "backup@pam", this returns the address from "root@pam".
|
||||
pub fn lookup_user_email(userid: &Userid) -> Option<String> {
|
||||
|
||||
use crate::config::user::{self, User};
|
||||
|
||||
if userid == Userid::backup_userid() {
|
||||
return lookup_user_email(Userid::root_userid());
|
||||
}
|
||||
|
||||
if let Ok(user_config) = user::cached_config() {
|
||||
if let Ok(user) = user_config.lookup::<User>("user", userid.as_str()) {
|
||||
return user.email.clone();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// Handlerbar helper functions
|
||||
|
||||
fn handlebars_humam_bytes_helper(
|
||||
h: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &Context,
|
||||
_rc: &mut RenderContext,
|
||||
out: &mut dyn Output
|
||||
) -> HelperResult {
|
||||
let param = h.param(0).map(|v| v.value().as_u64())
|
||||
.flatten()
|
||||
.ok_or(RenderError::new("human-bytes: param not found"))?;
|
||||
|
||||
out.write(&HumanByte::from(param).to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handlebars_relative_percentage_helper(
|
||||
h: &Helper,
|
||||
_: &Handlebars,
|
||||
_: &Context,
|
||||
_rc: &mut RenderContext,
|
||||
out: &mut dyn Output
|
||||
) -> HelperResult {
|
||||
let param0 = h.param(0).map(|v| v.value().as_f64())
|
||||
.flatten()
|
||||
.ok_or(RenderError::new("relative-percentage: param0 not found"))?;
|
||||
let param1 = h.param(1).map(|v| v.value().as_f64())
|
||||
.flatten()
|
||||
.ok_or(RenderError::new("relative-percentage: param1 not found"))?;
|
||||
|
||||
if param1 == 0.0 {
|
||||
out.write("-")?;
|
||||
} else {
|
||||
out.write(&format!("{:.2}%", (param0*100.0)/param1))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
@ -46,6 +46,8 @@ pub fn do_verification_job(
|
||||
})
|
||||
}
|
||||
|
||||
let email = crate::server::lookup_user_email(userid);
|
||||
|
||||
let job_id = job.jobname().to_string();
|
||||
let worker_type = job.jobtype().to_string();
|
||||
let upid_str = WorkerTask::new_thread(
|
||||
@ -103,6 +105,12 @@ pub fn do_verification_job(
|
||||
Ok(_) => (),
|
||||
}
|
||||
|
||||
if let Some(email) = email {
|
||||
if let Err(err) = crate::server::send_verify_status(&email, verification_job, &result) {
|
||||
eprintln!("send verify notification failed: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
},
|
||||
)?;
|
||||
|
Loading…
Reference in New Issue
Block a user