|
|
|
@ -1,7 +1,7 @@ |
|
|
|
|
use std::{ |
|
|
|
|
// process::Command,
|
|
|
|
|
time::SystemTime, |
|
|
|
|
cmp::PartialEq, |
|
|
|
|
time::{SystemTime, Duration}, |
|
|
|
|
thread, |
|
|
|
|
cmp::PartialEq |
|
|
|
|
}; |
|
|
|
|
use serde::{ |
|
|
|
|
Serialize, |
|
|
|
@ -10,7 +10,15 @@ use serde::{ |
|
|
|
|
use uuid::Uuid; |
|
|
|
|
use guess_host_triple::guess_host_triple; |
|
|
|
|
use tokio::process::Command; |
|
|
|
|
use crate::{models::schema::*, UError, UResult, UID, Waiter, OneOrMany}; |
|
|
|
|
use crate::{ |
|
|
|
|
models::schema::*, |
|
|
|
|
UError, |
|
|
|
|
UResult, |
|
|
|
|
UID, |
|
|
|
|
Waiter, |
|
|
|
|
OneOrMany, |
|
|
|
|
DynFut, |
|
|
|
|
}; |
|
|
|
|
use diesel_derive_enum::DbEnum; |
|
|
|
|
use diesel::{ |
|
|
|
|
Queryable, |
|
|
|
@ -53,12 +61,12 @@ pub enum JobType { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)] |
|
|
|
|
pub struct JobOutput<'s> { |
|
|
|
|
pub stdout: &'s [u8], |
|
|
|
|
pub stderr: &'s [u8], |
|
|
|
|
pub struct JobOutput { |
|
|
|
|
pub stdout: Vec<u8>, |
|
|
|
|
pub stderr: Vec<u8>, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl<'s, 'src: 's> JobOutput<'s> { |
|
|
|
|
impl JobOutput { |
|
|
|
|
const STREAM_BORDER: &'static str = "***"; |
|
|
|
|
const STDOUT: &'static str = "STDOUT"; |
|
|
|
|
const STDERR: &'static str = "STDERR"; |
|
|
|
@ -73,18 +81,18 @@ impl<'s, 'src: 's> JobOutput<'s> { |
|
|
|
|
|
|
|
|
|
pub fn new() -> Self { |
|
|
|
|
Self { |
|
|
|
|
stdout: &[], |
|
|
|
|
stderr: &[], |
|
|
|
|
stdout: vec![], |
|
|
|
|
stderr: vec![], |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn stdout(mut self, data: &'s [u8]) -> Self { |
|
|
|
|
self.stdout = data; |
|
|
|
|
pub fn stdout(mut self, data: &[u8]) -> Self { |
|
|
|
|
self.stdout = data.to_owned(); |
|
|
|
|
self |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn stderr(mut self, data: &'s [u8]) -> Self { |
|
|
|
|
self.stderr = data; |
|
|
|
|
pub fn stderr(mut self, data: &[u8]) -> Self { |
|
|
|
|
self.stderr = data.to_owned(); |
|
|
|
|
self |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -92,19 +100,19 @@ impl<'s, 'src: 's> JobOutput<'s> { |
|
|
|
|
let mut result: Vec<u8> = vec![]; |
|
|
|
|
if self.stdout.len() > 0 { |
|
|
|
|
result.extend(JobOutput::create_delim(JobOutput::STDOUT).into_bytes()); |
|
|
|
|
result.extend(self.stdout); |
|
|
|
|
result.extend(&self.stdout); |
|
|
|
|
result.push(b'\n'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if self.stderr.len() > 0 { |
|
|
|
|
result.extend(JobOutput::create_delim(JobOutput::STDERR).into_bytes()); |
|
|
|
|
result.extend(self.stderr); |
|
|
|
|
result.extend(&self.stderr); |
|
|
|
|
result.push(b'\n'); |
|
|
|
|
} |
|
|
|
|
result |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn from_raw(raw: &'src [u8]) -> Option<Self> { |
|
|
|
|
pub fn from_raw(raw: &[u8]) -> Option<Self> { |
|
|
|
|
let raw = String::from_utf8_lossy(raw); |
|
|
|
|
let err_header = JobOutput::create_delim(JobOutput::STDERR); |
|
|
|
|
raw.strip_prefix(&JobOutput::create_delim(JobOutput::STDOUT)) |
|
|
|
@ -119,13 +127,13 @@ impl<'s, 'src: 's> JobOutput<'s> { |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn into_appropriate(self) -> &'s [u8] { |
|
|
|
|
pub fn into_appropriate(self) -> Vec<u8> { |
|
|
|
|
if self.stdout.len() > 0 { |
|
|
|
|
self.stdout |
|
|
|
|
} else if self.stderr.len() > 0 { |
|
|
|
|
self.stderr |
|
|
|
|
} else { |
|
|
|
|
b"No data" |
|
|
|
|
b"No data".to_vec() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -264,54 +272,28 @@ impl Job { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn build_jobs<J: OneOrMany<JobMeta>>(job_metas: J) -> Waiter<_> { |
|
|
|
|
let prepared_jobs = job_metas.into_vec().into_iter().map(|job| { |
|
|
|
|
pub fn build_jobs<J: OneOrMany<JobMeta>>(job_metas: J) -> Waiter { |
|
|
|
|
let prepared_jobs = job_metas.into_vec().into_iter().map(|job| -> DynFut { |
|
|
|
|
let j = Job::build(job).unwrap(); |
|
|
|
|
j.run() |
|
|
|
|
}).collect(); |
|
|
|
|
Box::pin(j.run()) |
|
|
|
|
}).collect::<Vec<DynFut>>(); |
|
|
|
|
Waiter::new(prepared_jobs) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
pub async fn exec_jobs(jobs: Vec<JobMeta>) -> Vec<UResult<JobResult>> { |
|
|
|
|
let fids = exec_jobs_nowait(jobs).await.unwrap(); |
|
|
|
|
wait_for_tasks(fids).await |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub async fn exec_jobs_nowait(jobs: Vec<JobMeta>) -> UResult<Vec<Uuid>> { |
|
|
|
|
let prepared_jobs = jobs.into_iter().map(|job| { |
|
|
|
|
let j = Job::build(job).unwrap(); |
|
|
|
|
j.run() |
|
|
|
|
}).collect(); |
|
|
|
|
let fids = append_tasks(prepared_jobs).await; |
|
|
|
|
Ok(fids) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub async fn exec_job(job_meta: JobMeta) -> UResult<JobResult> { |
|
|
|
|
let job = Job::build(job_meta)?; |
|
|
|
|
run_until_complete(job.run()).await |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub async fn exec_job_nowait(job_meta: JobMeta) -> UResult<Uuid> { |
|
|
|
|
let job = Job::build(job_meta)?; |
|
|
|
|
let fid = append_task(job.run()).await; |
|
|
|
|
Ok(fid) |
|
|
|
|
} |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
#[cfg(test)] |
|
|
|
|
mod tests { |
|
|
|
|
use super::*; |
|
|
|
|
use crate::{build_jobs, utils::vec_to_string}; |
|
|
|
|
use crate::{build_jobs, utils::vec_to_string, pop_completed, spawn_dummy}; |
|
|
|
|
|
|
|
|
|
#[tokio::test] |
|
|
|
|
async fn test_is_really_async() { |
|
|
|
|
const SLEEP_SECS: u64 = 1; |
|
|
|
|
let job = JobMeta::from_shell(format!("sleep {}", SLEEP_SECS)); |
|
|
|
|
let sleep_jobs = vec![job.clone(), job.clone(), job.clone()]; |
|
|
|
|
let sleep_jobs: Vec<JobMeta> = (0..50).map(|_| job.clone()).collect(); |
|
|
|
|
let now = SystemTime::now(); |
|
|
|
|
let fids = build_jobs(sleep_jobs).run_until_complete().await; |
|
|
|
|
assert_eq!(now.elapsed().unwrap().as_secs(), SLEEP_SECS) |
|
|
|
|
build_jobs(sleep_jobs).run_until_complete().await; |
|
|
|
|
assert!(now.elapsed().unwrap().as_secs() < SLEEP_SECS+2) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[tokio::test] |
|
|
|
@ -320,30 +302,37 @@ mod tests { |
|
|
|
|
let job_result = build_jobs(job) |
|
|
|
|
.run_one_until_complete() |
|
|
|
|
.await; |
|
|
|
|
let stdout = JobOutput::from_raw(&job_result.result.unwrap()).unwrap().stdout; |
|
|
|
|
let stdout = JobOutput::from_raw( |
|
|
|
|
&job_result.unwrap().result.unwrap() |
|
|
|
|
).unwrap().stdout; |
|
|
|
|
assert_eq!( |
|
|
|
|
vec_to_string(stdout).trim(), |
|
|
|
|
vec_to_string(&stdout).trim(), |
|
|
|
|
"plazmoid" |
|
|
|
|
); |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[tokio::test] |
|
|
|
|
async fn test_complex_shell_jobs_load() -> UResult<()> { |
|
|
|
|
async fn test_complex_load() -> UResult<()> { |
|
|
|
|
const SLEEP_SECS: u64 = 1; |
|
|
|
|
let now = SystemTime::now(); |
|
|
|
|
let longest_job = JobMeta::from_shell(format!("sleep {}", SLEEP_SECS)); |
|
|
|
|
let longest_job = build_jobs(longest_job).spawn().await; |
|
|
|
|
let ls = build_jobs(JobMeta::from_shell("ls")) |
|
|
|
|
.run_one_until_complete() |
|
|
|
|
.await; |
|
|
|
|
.await |
|
|
|
|
.unwrap(); |
|
|
|
|
assert_eq!(ls.retcode.unwrap(), 0); |
|
|
|
|
let result = JobOutput::from_raw(&ls.result.unwrap()).unwrap(); |
|
|
|
|
let folders = String::from_utf8_lossy( |
|
|
|
|
&result.stdout |
|
|
|
|
); |
|
|
|
|
let subfolders_jobs: Vec<JobMeta> = folders |
|
|
|
|
.lines() |
|
|
|
|
.map(|f| JobMeta::from_shell(format!("ls {}", f))) |
|
|
|
|
.collect(); |
|
|
|
|
let ls_subfolders = build_jobs( |
|
|
|
|
folders.lines().map(|f| JobMeta::from_shell(format!("ls {}", f))).collect() |
|
|
|
|
subfolders_jobs |
|
|
|
|
).run_until_complete().await; |
|
|
|
|
for result in ls_subfolders { |
|
|
|
|
assert_eq!(result.unwrap().retcode.unwrap(), 0); |
|
|
|
@ -353,10 +342,27 @@ mod tests { |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[tokio::test] |
|
|
|
|
async fn test_exec_multiple_jobs_nowait() -> UResult<()> { |
|
|
|
|
const REPEATS: usize = 10; |
|
|
|
|
let job = JobMeta::from_shell("whoami"); |
|
|
|
|
let sleep_jobs: Vec<JobMeta> = (0..=REPEATS).map(|_| job.clone()).collect(); |
|
|
|
|
build_jobs(sleep_jobs).spawn().await; |
|
|
|
|
let mut completed = 0; |
|
|
|
|
while completed < REPEATS { |
|
|
|
|
let c = pop_completed().await.len(); |
|
|
|
|
if c > 0 { |
|
|
|
|
completed += c; |
|
|
|
|
println!("{}", c); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[tokio::test] |
|
|
|
|
async fn test_failing_shell_job() -> UResult<()> { |
|
|
|
|
let job = JobMeta::from_shell("lol_kek_puk"); |
|
|
|
|
let job_result = build_jobs(job).run_one_until_complete().await; |
|
|
|
|
let job_result = build_jobs(job).run_one_until_complete().await.unwrap(); |
|
|
|
|
let output = JobOutput::from_raw(&job_result.result.unwrap()); |
|
|
|
|
assert!(output.is_none()); |
|
|
|
|
assert!(job_result.retcode.is_none()); |
|
|
|
@ -366,29 +372,29 @@ mod tests { |
|
|
|
|
#[test] |
|
|
|
|
fn test_to_multiline() { |
|
|
|
|
let mut output = JobOutput::new(); |
|
|
|
|
output.stdout = b"lol"; |
|
|
|
|
output.stderr = b"kek"; |
|
|
|
|
output.stdout = b"lol".to_vec(); |
|
|
|
|
output.stderr = b"kek".to_vec(); |
|
|
|
|
assert_eq!( |
|
|
|
|
output.multiline(), |
|
|
|
|
vec_to_string(&output.multiline()), |
|
|
|
|
String::from( |
|
|
|
|
"*** STDOUT ***\n\ |
|
|
|
|
lol\n\ |
|
|
|
|
*** STDERR ***\n\ |
|
|
|
|
kek\n" |
|
|
|
|
).into_bytes() |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
fn test_to_multiline_stderr_only() { |
|
|
|
|
let mut output = JobOutput::new(); |
|
|
|
|
output.stderr = b"kek"; |
|
|
|
|
output.stderr = b"kek".to_vec(); |
|
|
|
|
assert_eq!( |
|
|
|
|
output.multiline(), |
|
|
|
|
vec_to_string(&output.multiline()), |
|
|
|
|
String::from( |
|
|
|
|
"*** STDERR ***\n\ |
|
|
|
|
kek\n" |
|
|
|
|
).into_bytes() |
|
|
|
|
) |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -398,8 +404,8 @@ mod tests { |
|
|
|
|
puk\n".as_bytes(); |
|
|
|
|
let output = JobOutput::from_raw(txt).unwrap(); |
|
|
|
|
assert_eq!( |
|
|
|
|
output.stdout, |
|
|
|
|
b"puk".to_vec() |
|
|
|
|
vec_to_string(&output.stdout), |
|
|
|
|
"puk".to_string() |
|
|
|
|
); |
|
|
|
|
assert_eq!(output.stderr.len(), 0); |
|
|
|
|
} |
|
|
|
|