//! an example usage would be
//! ```no_run
//! # use anyhow::{bail, Error};
-//! # use proxmox_backup::server::TaskState;
-//! # use proxmox_backup::config::jobstate::*;
+//! # use proxmox_rest_server::TaskState;
+//! # use proxmox_backup::server::jobstate::*;
//! # fn some_code() -> TaskState { TaskState::OK { endtime: 0 } }
//! # fn code() -> Result<(), Error> {
//! // locks the correct file under /var/lib
//! # }
//!
//! ```
-use std::fs::File;
use std::path::{Path, PathBuf};
-use std::time::Duration;
use anyhow::{bail, format_err, Error};
+use serde::{Deserialize, Serialize};
+
use proxmox::tools::fs::{
- create_path, file_read_optional_string, open_file_locked, replace_file, CreateOptions,
+ create_path, file_read_optional_string, replace_file, CreateOptions,
};
-use serde::{Deserialize, Serialize};
-use crate::server::{upid_read_status, worker_is_active_local, TaskState, UPID};
+use proxmox_systemd::time::{compute_next_event, parse_calendar_event};
+use pbs_config::{open_backup_lockfile, BackupLockGuard};
+use pbs_api_types::{UPID, JobScheduleStatus};
+
+use proxmox_rest_server::{upid_read_status, worker_is_active_local, TaskState};
-#[serde(rename_all = "kebab-case")]
#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
/// Represents the State of a specific Job
pub enum JobState {
/// A job was created at 'time', but never started/finished
Created { time: i64 },
/// The Job was last started in 'upid',
Started { upid: String },
- /// The Job was last started in 'upid', which finished with 'state'
- Finished { upid: String, state: TaskState },
+ /// The Job was last started in 'upid', which finished with 'state', and was last updated at 'updated'
+ Finished {
+ upid: String,
+ state: TaskState,
+ updated: Option<i64>,
+ },
}
/// Represents a Job and holds the correct lock
jobname: String,
/// The State of the job
pub state: JobState,
- _lock: File,
+ _lock: BackupLockGuard,
}
const JOB_STATE_BASEDIR: &str = "/var/lib/proxmox-backup/jobstates";
/// Create jobstate stat dir with correct permission
pub fn create_jobstate_dir() -> Result<(), Error> {
- let backup_user = crate::backup::backup_user()?;
+ let backup_user = pbs_config::backup_user()?;
let opts = CreateOptions::new()
.owner(backup_user.uid)
.group(backup_user.gid);
path
}
-fn get_lock<P>(path: P) -> Result<File, Error>
+fn get_lock<P>(path: P) -> Result<BackupLockGuard, Error>
where
P: AsRef<Path>,
{
let mut path = path.as_ref().to_path_buf();
path.set_extension("lck");
- let lock = open_file_locked(&path, Duration::new(10, 0), true)?;
- let backup_user = crate::backup::backup_user()?;
- nix::unistd::chown(&path, Some(backup_user.uid), Some(backup_user.gid))?;
- Ok(lock)
+ open_backup_lockfile(&path, None, true)
}
/// Removes the statefile of a job, this is useful if we delete a job
job.write_state()
}
+/// Tries to update the state file with the current time
+/// if the job is currently running, does nothing.
+/// Intended for use when the schedule changes.
+pub fn update_job_last_run_time(jobtype: &str, jobname: &str) -> Result<(), Error> {
+ let mut job = match Job::new(jobtype, jobname) {
+ Ok(job) => job,
+ Err(_) => return Ok(()), // was locked (running), so do not update
+ };
+ let time = proxmox_time::epoch_i64();
+
+ job.state = match JobState::load(jobtype, jobname)? {
+ JobState::Created { .. } => JobState::Created { time },
+ JobState::Started { .. } => return Ok(()), // currently running (without lock?)
+ JobState::Finished {
+ upid,
+ state,
+ updated: _,
+ } => JobState::Finished {
+ upid,
+ state,
+ updated: Some(time),
+ },
+ };
+ job.write_state()
+}
+
/// Returns the last run time of a job by reading the statefile
/// Note that this is not locked
pub fn last_run_time(jobtype: &str, jobname: &str) -> Result<i64, Error> {
match JobState::load(jobtype, jobname)? {
JobState::Created { time } => Ok(time),
- JobState::Started { upid } | JobState::Finished { upid, .. } => {
+ JobState::Finished {
+ updated: Some(time),
+ ..
+ } => Ok(time),
+ JobState::Started { upid }
+ | JobState::Finished {
+ upid,
+ state: _,
+ updated: None,
+ } => {
let upid: UPID = upid
.parse()
.map_err(|err| format_err!("could not parse upid from state: {}", err))?;
let state = upid_read_status(&parsed)
.map_err(|err| format_err!("error reading upid log status: {}", err))?;
- Ok(JobState::Finished { upid, state })
+ Ok(JobState::Finished {
+ upid,
+ state,
+ updated: None,
+ })
} else {
Ok(JobState::Started { upid })
}
}
} else {
Ok(JobState::Created {
- time: proxmox::tools::time::epoch_i64() - 30,
+ time: proxmox_time::epoch_i64() - 30,
})
}
}
jobtype: jobtype.to_string(),
jobname: jobname.to_string(),
state: JobState::Created {
- time: proxmox::tools::time::epoch_i64(),
+ time: proxmox_time::epoch_i64(),
},
_lock,
})
/// Start the job and update the statefile accordingly
/// Fails if the job was already started
pub fn start(&mut self, upid: &str) -> Result<(), Error> {
- match self.state {
- JobState::Started { .. } => {
- bail!("cannot start job that is started!");
- }
- _ => {}
+ if let JobState::Started { .. } = self.state {
+ bail!("cannot start job that is started!");
}
self.state = JobState::Started {
}
.to_string();
- self.state = JobState::Finished { upid, state };
+ self.state = JobState::Finished {
+ upid,
+ state,
+ updated: None,
+ };
self.write_state()
}
let serialized = serde_json::to_string(&self.state)?;
let path = get_path(&self.jobtype, &self.jobname);
- let backup_user = crate::backup::backup_user()?;
+ let backup_user = pbs_config::backup_user()?;
let mode = nix::sys::stat::Mode::from_bits_truncate(0o0644);
// set the correct owner/group/permissions while saving file
// owner(rw) = backup, group(r)= backup
.owner(backup_user.uid)
.group(backup_user.gid);
- replace_file(path, serialized.as_bytes(), options)
+ replace_file(path, serialized.as_bytes(), options, false)
+ }
+}
+
+pub fn compute_schedule_status(
+ job_state: &JobState,
+ schedule: Option<&str>,
+) -> Result<JobScheduleStatus, Error> {
+ let (upid, endtime, state, last) = match job_state {
+ JobState::Created { time } => (None, None, None, *time),
+ JobState::Started { upid } => {
+ let parsed_upid: UPID = upid.parse()?;
+ (Some(upid), None, None, parsed_upid.starttime)
+ }
+ JobState::Finished {
+ upid,
+ state,
+ updated,
+ } => {
+ let last = updated.unwrap_or_else(|| state.endtime());
+ (
+ Some(upid),
+ Some(state.endtime()),
+ Some(state.to_string()),
+ last,
+ )
+ }
+ };
+
+ let mut status = JobScheduleStatus::default();
+ status.last_run_upid = upid.map(String::from);
+ status.last_run_state = state;
+ status.last_run_endtime = endtime;
+
+ if let Some(schedule) = schedule {
+ if let Ok(event) = parse_calendar_event(&schedule) {
+ // ignore errors
+ status.next_run = compute_next_event(&event, last, false).unwrap_or(None);
+ }
}
+
+ Ok(status)
}