|
|
|
@ -1,16 +1,24 @@ |
|
|
|
|
use std::{ |
|
|
|
|
process::Command, |
|
|
|
|
// process::Command,
|
|
|
|
|
time::SystemTime, |
|
|
|
|
cmp::PartialEq, |
|
|
|
|
sync::{Arc, Mutex, MutexGuard}, |
|
|
|
|
}; |
|
|
|
|
use serde::{ |
|
|
|
|
Serialize, |
|
|
|
|
Deserialize |
|
|
|
|
}; |
|
|
|
|
use uuid::Uuid; |
|
|
|
|
//use tokio::process::Command;
|
|
|
|
|
use tokio::process::Command; |
|
|
|
|
use super::*; |
|
|
|
|
use crate::{UError, UErrType}; |
|
|
|
|
use crate::{ |
|
|
|
|
UError, |
|
|
|
|
UErrType, |
|
|
|
|
UErrType::JobError, |
|
|
|
|
BoxError, |
|
|
|
|
JobErrType, |
|
|
|
|
UResult, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)] |
|
|
|
@ -29,7 +37,7 @@ pub enum JobSchedule { |
|
|
|
|
//TODO: Scheduled
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)] |
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] |
|
|
|
|
pub enum JobState { |
|
|
|
|
Queued, // server created a job, but client didn't get it yet
|
|
|
|
|
Pending, // client got a job, but not running yet
|
|
|
|
@ -75,13 +83,15 @@ impl JobMeta { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn into_arc(self) -> JobMetaRef { |
|
|
|
|
Arc::new(Mutex::new(self)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn touch(&mut self) { |
|
|
|
|
self.updated = SystemTime::now(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl ToMsg for JobMeta {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)] |
|
|
|
|
pub struct JobOutput { |
|
|
|
@ -109,6 +119,16 @@ impl JobOutput { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn stdout(mut self, data: Vec<u8>) -> Self { |
|
|
|
|
self.stdout = data; |
|
|
|
|
self |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn stderr(mut self, data: Vec<u8>) -> Self { |
|
|
|
|
self.stderr = data; |
|
|
|
|
self |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn multiline(&self) -> String { |
|
|
|
|
let mut result = String::new(); |
|
|
|
|
if self.stdout.len() > 0 { |
|
|
|
@ -133,10 +153,9 @@ impl JobOutput { |
|
|
|
|
.map(|d| Vec::from(d.trim().as_bytes())) |
|
|
|
|
.collect::<Vec<Vec<u8>>>() |
|
|
|
|
.into_iter(); |
|
|
|
|
let mut instance = JobOutput::new(); |
|
|
|
|
instance.stdout = parts.next().unwrap(); |
|
|
|
|
instance.stderr = parts.next().unwrap_or(vec![]); |
|
|
|
|
instance |
|
|
|
|
JobOutput::new() |
|
|
|
|
.stdout(parts.next().unwrap()) |
|
|
|
|
.stderr(parts.next().unwrap_or(vec![])) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -144,83 +163,102 @@ impl JobOutput { |
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)] |
|
|
|
|
pub struct JobResult { |
|
|
|
|
pub id: Uuid, |
|
|
|
|
pub data: Result<JobOutput, UError>, |
|
|
|
|
pub data: Option<Result<JobOutput, UError>>, |
|
|
|
|
pub state: JobState, |
|
|
|
|
pub retcode: Option<i32>, |
|
|
|
|
pub date: SystemTime, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl ToMsg for JobResult {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub struct Job<'meta> { |
|
|
|
|
pub struct Job { |
|
|
|
|
result: JobResult, |
|
|
|
|
meta: &'meta mut JobMeta, |
|
|
|
|
meta: JobMetaRef, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl<'meta> Job<'meta> { |
|
|
|
|
pub fn new(job_meta: &'meta mut JobMeta) -> Self { |
|
|
|
|
impl Job { |
|
|
|
|
pub fn new(job_meta: JobMetaRef) -> Self { |
|
|
|
|
let id = job_meta.lock().unwrap().id.clone(); |
|
|
|
|
let state = job_meta.lock().unwrap().state.clone(); |
|
|
|
|
Self { |
|
|
|
|
result: JobResult { |
|
|
|
|
id: job_meta.id.clone(), |
|
|
|
|
state: job_meta.state.clone(), |
|
|
|
|
data: Ok(JobOutput::new()), |
|
|
|
|
id, |
|
|
|
|
state: if state == JobState::Queued { |
|
|
|
|
JobState::Pending |
|
|
|
|
} else { |
|
|
|
|
state |
|
|
|
|
}, |
|
|
|
|
data: None, |
|
|
|
|
retcode: None, |
|
|
|
|
}, |
|
|
|
|
meta: job_meta, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn run(&mut self) { |
|
|
|
|
match self.meta.exec_type { |
|
|
|
|
pub async fn run(mut self) -> UResult<JobResult> { |
|
|
|
|
match self.exec_type() { |
|
|
|
|
JobType::Shell => { |
|
|
|
|
match self.meta.state { |
|
|
|
|
match self.state() { |
|
|
|
|
JobState::Queued | JobState::Pending => { |
|
|
|
|
self.update_state(Some(JobState::Running)); |
|
|
|
|
}, |
|
|
|
|
JobState::Finished => { |
|
|
|
|
if self.meta.schedule == JobSchedule::Permanent { |
|
|
|
|
if self.schedule() == JobSchedule::Permanent { |
|
|
|
|
self.update_state(Some(JobState::Running)) |
|
|
|
|
} else { |
|
|
|
|
return |
|
|
|
|
return Err(UError::new_type( |
|
|
|
|
JobError(JobErrType::Finished) |
|
|
|
|
)) |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
JobState::Running => return |
|
|
|
|
JobState::Running => return Err(UError::new_type( |
|
|
|
|
JobError(JobErrType::AlreadyRunning) |
|
|
|
|
)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
match &self.meta.payload { |
|
|
|
|
let str_payload = match &self.lock().payload { |
|
|
|
|
Some(box_payload) => { |
|
|
|
|
let payload = String::from_utf8_lossy(box_payload).into_owned(); |
|
|
|
|
let mut cmd_parts = payload |
|
|
|
|
.split(" ") |
|
|
|
|
.map(String::from) |
|
|
|
|
.collect::<Vec<String>>() |
|
|
|
|
.into_iter(); |
|
|
|
|
let cmd = cmd_parts.nth(0).unwrap(); |
|
|
|
|
let args = cmd_parts.collect::<Vec<_>>(); |
|
|
|
|
let result = Command::new(cmd) |
|
|
|
|
.args(args) |
|
|
|
|
.output(); |
|
|
|
|
match result { |
|
|
|
|
Ok(output) => { |
|
|
|
|
let job_out: &mut JobOutput = self.result.data.as_mut().unwrap(); |
|
|
|
|
job_out.stdout = output.stdout.to_vec(); |
|
|
|
|
job_out.stderr = output.stderr.to_vec(); |
|
|
|
|
self.result.retcode = output.status.code(); |
|
|
|
|
} |
|
|
|
|
Err(e) => { |
|
|
|
|
self.result.data = Err( |
|
|
|
|
UError::new(UErrType::JobError, e.to_string()) |
|
|
|
|
); |
|
|
|
|
self.result.retcode = None; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
String::from_utf8_lossy(box_payload).into_owned() |
|
|
|
|
} |
|
|
|
|
None => return |
|
|
|
|
} |
|
|
|
|
self.meta.state = JobState::Finished; |
|
|
|
|
None => unimplemented!() |
|
|
|
|
}; |
|
|
|
|
let mut cmd_parts = str_payload |
|
|
|
|
.split(" ") |
|
|
|
|
.map(String::from) |
|
|
|
|
.collect::<Vec<String>>() |
|
|
|
|
.into_iter(); |
|
|
|
|
let cmd = cmd_parts.nth(0).unwrap(); |
|
|
|
|
let args = cmd_parts.collect::<Vec<_>>(); |
|
|
|
|
let cmd_result = Command::new(cmd) |
|
|
|
|
.args(args) |
|
|
|
|
.output() |
|
|
|
|
.await; |
|
|
|
|
let (data, retcode) = match cmd_result { |
|
|
|
|
Ok(output) => { |
|
|
|
|
( |
|
|
|
|
Some(Ok(JobOutput::new() |
|
|
|
|
.stdout(output.stdout.to_vec()) |
|
|
|
|
.stderr(output.stderr.to_vec())) |
|
|
|
|
), |
|
|
|
|
output.status.code() |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
Err(e) => { |
|
|
|
|
( |
|
|
|
|
Some(Err(UError::new( |
|
|
|
|
UErrType::JobError(JobErrType::System), |
|
|
|
|
e.to_string() |
|
|
|
|
))), |
|
|
|
|
None |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
self.update_state(Some(JobState::Finished)); |
|
|
|
|
self.result.data = data; |
|
|
|
|
self.result.retcode = retcode; |
|
|
|
|
}, |
|
|
|
|
_ => unimplemented!() |
|
|
|
|
} |
|
|
|
|
Ok(self.into_result()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// None => state is copied from meta to result field
|
|
|
|
@ -228,17 +266,40 @@ impl<'meta> Job<'meta> { |
|
|
|
|
pub fn update_state(&mut self, state: Option<JobState>) { |
|
|
|
|
match state { |
|
|
|
|
Some(state) => { |
|
|
|
|
self.meta.state = state.clone(); |
|
|
|
|
self.meta.lock().unwrap().state = state.clone(); |
|
|
|
|
self.result.state = state; |
|
|
|
|
} |
|
|
|
|
None => { |
|
|
|
|
self.result.state = self.meta.state.clone(); |
|
|
|
|
self.result.state = self.state(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn into_result(mut self) -> JobResult { |
|
|
|
|
Self::update_state(&mut self, None); |
|
|
|
|
fn lock(&self) -> MutexGuard<JobMeta> { |
|
|
|
|
self.meta.lock().unwrap() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn id(&self) -> Uuid { |
|
|
|
|
self.lock().id.clone() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn state(&self) -> JobState { |
|
|
|
|
self.lock().state.clone() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn exec_type(&self) -> JobType { |
|
|
|
|
self.lock().exec_type.clone() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn schedule(&self) -> JobSchedule { |
|
|
|
|
self.lock().schedule.clone() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn finished(&self) -> bool { |
|
|
|
|
self.state() == JobState::Finished |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn into_result(self) -> JobResult { |
|
|
|
|
self.result |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -248,30 +309,39 @@ impl<'meta> Job<'meta> { |
|
|
|
|
mod tests { |
|
|
|
|
use super::*; |
|
|
|
|
use crate::{ |
|
|
|
|
execute_jobs, |
|
|
|
|
send_jobs_to_executor, |
|
|
|
|
exec_job, |
|
|
|
|
utils::vec_to_string |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
fn test_shell_job() { |
|
|
|
|
let mut job = JobMeta::from_shell("whoami".into()); |
|
|
|
|
let mut jobs: Vec<Job> = vec![Job::new(&mut job)]; |
|
|
|
|
execute_jobs(&mut jobs); |
|
|
|
|
let job_result = jobs.pop().unwrap().into_result(); |
|
|
|
|
#[tokio::test] |
|
|
|
|
async fn test_is_really_async() { |
|
|
|
|
let secs_to_sleep = 1; |
|
|
|
|
let job = JobMeta::from_shell(format!("sleep {}", secs_to_sleep)).into_arc(); |
|
|
|
|
let sleep_jobs = vec![job.clone(), job.clone(), job.clone()]; |
|
|
|
|
let now = SystemTime::now(); |
|
|
|
|
send_jobs_to_executor(sleep_jobs).await; |
|
|
|
|
assert_eq!(now.elapsed().unwrap().as_secs(), secs_to_sleep) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[tokio::test] |
|
|
|
|
async fn test_shell_job() -> UResult<()> { |
|
|
|
|
let job = JobMeta::from_shell("whoami".into()).into_arc(); |
|
|
|
|
let job_result = exec_job(job.clone()).await.unwrap(); |
|
|
|
|
assert_eq!( |
|
|
|
|
&vec_to_string(&job_result.data.unwrap().stdout), |
|
|
|
|
"plazmoid" |
|
|
|
|
vec_to_string(&job_result.data.unwrap()?.stdout).trim(), |
|
|
|
|
"root" |
|
|
|
|
); |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
fn test_failing_shell_job() { |
|
|
|
|
let mut job = JobMeta::from_shell("lol_kek_puk".into()); |
|
|
|
|
let mut jobs: Vec<Job> = vec![Job::new(&mut job)]; |
|
|
|
|
execute_jobs(&mut jobs); |
|
|
|
|
let job_result = jobs.pop().unwrap().into_result(); |
|
|
|
|
assert!(job_result.data.is_err()); |
|
|
|
|
#[tokio::test] |
|
|
|
|
async fn test_failing_shell_job() -> UResult<()> { |
|
|
|
|
let job = JobMeta::from_shell("lol_kek_puk".into()).into_arc(); |
|
|
|
|
let job_result = exec_job(job.clone()).await.unwrap(); |
|
|
|
|
assert!(job_result.data.unwrap().is_err()); |
|
|
|
|
assert_eq!(job_result.retcode, None); |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|