|
|
|
@ -1,11 +1,15 @@ |
|
|
|
|
use std::{ |
|
|
|
|
time::{SystemTime, Duration}, |
|
|
|
|
thread, |
|
|
|
|
sync::{RwLock, RwLockReadGuard}, |
|
|
|
|
cmp::PartialEq, |
|
|
|
|
fmt, |
|
|
|
|
string::ToString, |
|
|
|
|
path::PathBuf, |
|
|
|
|
fs |
|
|
|
|
fs, |
|
|
|
|
process::Output, |
|
|
|
|
collections::HashMap, |
|
|
|
|
ops::Deref, |
|
|
|
|
}; |
|
|
|
|
use serde::{ |
|
|
|
|
Serialize, |
|
|
|
@ -13,10 +17,13 @@ use serde::{ |
|
|
|
|
}; |
|
|
|
|
use uuid::Uuid; |
|
|
|
|
use guess_host_triple::guess_host_triple; |
|
|
|
|
use tokio::process::Command; |
|
|
|
|
use tokio::{ |
|
|
|
|
process::Command |
|
|
|
|
}; |
|
|
|
|
use crate::{ |
|
|
|
|
utils::systime_to_string, |
|
|
|
|
models::schema::*, |
|
|
|
|
Agent, |
|
|
|
|
UError, |
|
|
|
|
UResult, |
|
|
|
|
UID, |
|
|
|
@ -29,10 +36,49 @@ use diesel::{ |
|
|
|
|
Queryable, |
|
|
|
|
Identifiable, |
|
|
|
|
Insertable, |
|
|
|
|
query_builder::AsChangeset |
|
|
|
|
}; |
|
|
|
|
use strum::Display; |
|
|
|
|
|
|
|
|
|
type Cache = HashMap<Uuid, JobMeta>; |
|
|
|
|
|
|
|
|
|
lazy_static! { |
|
|
|
|
static ref JOB_CACHE: RwLock<Cache> = RwLock::new(HashMap::new()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub struct JobCache; |
|
|
|
|
|
|
|
|
|
impl JobCache { |
|
|
|
|
pub fn insert(job_meta: JobMeta) { |
|
|
|
|
JOB_CACHE.write().unwrap().insert(job_meta.id, job_meta); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn contains(uid: &Uuid) -> bool { |
|
|
|
|
JOB_CACHE.read().unwrap().contains_key(uid) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn get(uid: &Uuid) -> Option<JobCacheHolder> { |
|
|
|
|
if !Self::contains(uid) { |
|
|
|
|
return None |
|
|
|
|
} |
|
|
|
|
let lock = JOB_CACHE.read().unwrap(); |
|
|
|
|
Some(JobCacheHolder(lock, uid)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn remove(uid: &Uuid) { |
|
|
|
|
JOB_CACHE.write().unwrap().remove(uid); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub struct JobCacheHolder<'jm>(pub RwLockReadGuard<'jm, Cache>, pub &'jm Uuid); |
|
|
|
|
|
|
|
|
|
impl<'jm> Deref for JobCacheHolder<'jm> { |
|
|
|
|
type Target = JobMeta; |
|
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target { |
|
|
|
|
self.0.get(self.1).unwrap() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)] |
|
|
|
|
pub enum ManageAction { |
|
|
|
@ -46,7 +92,7 @@ pub enum ManageAction { |
|
|
|
|
pub enum JobSchedule { |
|
|
|
|
Once, |
|
|
|
|
Permanent, |
|
|
|
|
//TODO: Scheduled
|
|
|
|
|
//Scheduled
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, DbEnum, Display)] |
|
|
|
@ -96,13 +142,13 @@ impl JobOutput { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn stdout(mut self, data: &[u8]) -> Self { |
|
|
|
|
self.stdout = data.to_owned(); |
|
|
|
|
pub fn stdout(mut self, data: Vec<u8>) -> Self { |
|
|
|
|
self.stdout = data; |
|
|
|
|
self |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn stderr(mut self, data: &[u8]) -> Self { |
|
|
|
|
self.stderr = data.to_owned(); |
|
|
|
|
pub fn stderr(mut self, data: Vec<u8>) -> Self { |
|
|
|
|
self.stderr = data; |
|
|
|
|
self |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -128,12 +174,12 @@ impl JobOutput { |
|
|
|
|
raw.strip_prefix(&JobOutput::create_delim(JobOutput::STDOUT)) |
|
|
|
|
.map(|s: &str| { |
|
|
|
|
let mut parts = s.split(&err_header) |
|
|
|
|
.map(|d| d.trim().as_bytes()) |
|
|
|
|
.collect::<Vec<&[u8]>>() |
|
|
|
|
.map(|d| d.trim().as_bytes().to_vec()) |
|
|
|
|
.collect::<Vec<Vec<u8>>>() |
|
|
|
|
.into_iter(); |
|
|
|
|
JobOutput::new() |
|
|
|
|
.stdout(parts.next().unwrap()) |
|
|
|
|
.stderr(parts.next().unwrap_or(&[])) |
|
|
|
|
.stderr(parts.next().unwrap_or(vec![])) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -158,7 +204,7 @@ impl JobOutput { |
|
|
|
|
Insertable |
|
|
|
|
)] |
|
|
|
|
#[table_name = "jobs"] |
|
|
|
|
pub struct JobMeta { // TODO: shell cmd how to exec payload
|
|
|
|
|
pub struct JobMeta { |
|
|
|
|
pub alias: String, |
|
|
|
|
pub id: Uuid, |
|
|
|
|
pub exec_type: JobType, |
|
|
|
@ -223,7 +269,7 @@ impl Default for JobMeta { |
|
|
|
|
Queryable, |
|
|
|
|
Identifiable, |
|
|
|
|
Insertable, |
|
|
|
|
AsChangeset |
|
|
|
|
AsChangeset, |
|
|
|
|
)] |
|
|
|
|
#[table_name = "results"] |
|
|
|
|
pub struct JobResult { |
|
|
|
@ -257,11 +303,13 @@ impl fmt::Display for JobResult { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl JobResult { |
|
|
|
|
pub fn from_meta(job_id: Uuid) -> Self { |
|
|
|
|
let mut inst = JobResult::default(); |
|
|
|
|
inst.agent_id = *UID; |
|
|
|
|
inst.job_id = job_id; |
|
|
|
|
inst |
|
|
|
|
pub fn from_meta(job_id: Uuid, result_id: Option<Uuid>) -> Self { |
|
|
|
|
Self { |
|
|
|
|
id: result_id.unwrap_or(Uuid::new_v4()), |
|
|
|
|
agent_id: *UID, |
|
|
|
|
job_id, |
|
|
|
|
..Default::default() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//pub fn as_job_output(&self) -> JobOutput {}
|
|
|
|
@ -289,17 +337,20 @@ pub struct Job { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Job { |
|
|
|
|
fn build(job_meta: JobMeta) -> UResult<Self> { |
|
|
|
|
fn build(job_meta: &JobMeta, result_id: Uuid) -> UResult<Self> { |
|
|
|
|
match job_meta.exec_type { |
|
|
|
|
JobType::Shell => { |
|
|
|
|
let curr_platform = guess_host_triple().unwrap_or("unknown").to_string(); |
|
|
|
|
if job_meta.platform != curr_platform { |
|
|
|
|
return Err(UError::InsuitablePlatform(job_meta.platform, curr_platform)) |
|
|
|
|
return Err(UError::InsuitablePlatform( |
|
|
|
|
job_meta.platform.clone(), curr_platform |
|
|
|
|
)) |
|
|
|
|
} |
|
|
|
|
let job_meta = job_meta.clone(); |
|
|
|
|
Ok(Self { |
|
|
|
|
exec_type: job_meta.exec_type, |
|
|
|
|
payload: job_meta.payload, |
|
|
|
|
result: JobResult::from_meta(job_meta.id.clone()) |
|
|
|
|
result: JobResult::from_meta(job_meta.id.clone(), Some(result_id)) |
|
|
|
|
}) |
|
|
|
|
}, |
|
|
|
|
_ => todo!() |
|
|
|
@ -315,7 +366,7 @@ impl Job { |
|
|
|
|
} |
|
|
|
|
None => unimplemented!() |
|
|
|
|
}; |
|
|
|
|
let mut cmd_parts = str_payload // TODO: WRONG
|
|
|
|
|
let mut cmd_parts = str_payload |
|
|
|
|
.split(" ") |
|
|
|
|
.map(String::from) |
|
|
|
|
.collect::<Vec<String>>() |
|
|
|
@ -327,14 +378,14 @@ impl Job { |
|
|
|
|
.output() |
|
|
|
|
.await; |
|
|
|
|
let (data, retcode) = match cmd_result { |
|
|
|
|
Ok(output) => { |
|
|
|
|
Ok(Output {status, stdout, stderr}) => { |
|
|
|
|
( |
|
|
|
|
Some(JobOutput::new() |
|
|
|
|
.stdout(&output.stdout) |
|
|
|
|
.stderr(&output.stderr) |
|
|
|
|
.stdout(stdout) |
|
|
|
|
.stderr(stderr) |
|
|
|
|
.multiline() |
|
|
|
|
), |
|
|
|
|
output.status.code() |
|
|
|
|
status.code() |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
Err(e) => { |
|
|
|
@ -355,14 +406,38 @@ impl 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(); |
|
|
|
|
Box::pin(j.run()) |
|
|
|
|
pub fn build_jobs_with_result<J: OneOrMany<JobResult>>(job_requests: J) -> Waiter { |
|
|
|
|
let prepared_jobs = job_requests.into_vec() |
|
|
|
|
.into_iter() |
|
|
|
|
.filter_map(|jr| -> Option<DynFut> { |
|
|
|
|
let job = { |
|
|
|
|
let job_meta = JobCache::get(&jr.job_id); |
|
|
|
|
if job_meta.is_none() { |
|
|
|
|
Err(UError::NoJob(jr.job_id)) |
|
|
|
|
} else { |
|
|
|
|
Job::build(&*job_meta.unwrap(), jr.id) |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
match job { |
|
|
|
|
Ok(j) => Some(Box::pin(j.run())), |
|
|
|
|
Err(e) => { |
|
|
|
|
warn!("Job building error: {}", e); |
|
|
|
|
None |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}).collect::<Vec<DynFut>>(); |
|
|
|
|
Waiter::new(prepared_jobs) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn build_jobs<J: OneOrMany<JobMeta>>(job_metas: J) -> Waiter { |
|
|
|
|
let job_requests = job_metas.into_vec().into_iter().map(|jm| { |
|
|
|
|
let j_uid = jm.id; |
|
|
|
|
JobCache::insert(jm); |
|
|
|
|
JobResult::from_meta(j_uid, None) |
|
|
|
|
}).collect::<Vec<JobResult>>(); |
|
|
|
|
build_jobs_with_result(job_requests) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)] |
|
|
|
|
mod tests { |
|
|
|
|