use std::{
sync::Arc,
time::{Duration, Instant},
};
use futures_timer::Delay;
use tracing::debug;
use crate::{
client::{HttpClient, PackagesClient},
schema::jobs::{
JobDeleteResponse, JobDetails, JobLogsResponse, JobStatus, JobStopResponse, JobSubmitRequest,
JobSubmitResponse,
},
};
#[derive(Debug, Clone)]
pub struct JobsClient {
http: Arc<HttpClient>,
}
impl JobsClient {
pub(crate) fn new(http: Arc<HttpClient>) -> Self {
Self { http }
}
pub async fn submit(&self, payload: &JobSubmitRequest) -> crate::Result<JobSubmitResponse> {
let mut payload = payload.clone();
if let Some(ref mut runtime_env) = payload.runtime_env
&& let Some(ref working_dir_path) = runtime_env.working_dir
{
debug!("Uploading working directory: {:?}", working_dir_path);
let package_uri = PackagesClient::new(self.http.clone())
.upload_directory(working_dir_path)
.await?;
debug!("Working directory uploaded, URI: {}", package_uri);
runtime_env.working_dir = Some(package_uri.to_string().into());
}
let path = "/api/jobs/";
let request = self.http.request(reqwest::Method::POST, path)?;
let response = request.json(&payload).send().await?.error_for_status()?;
Ok(response.json::<JobSubmitResponse>().await?)
}
pub async fn list(&self) -> crate::Result<Vec<JobDetails>> {
let path = "/api/jobs/";
let request = self.http.request(reqwest::Method::GET, path)?;
let response = request.send().await?.error_for_status()?;
Ok(response.json::<Vec<JobDetails>>().await?)
}
pub async fn get(&self, submission_id: &str) -> crate::Result<JobDetails> {
let path = format!("/api/jobs/{}", submission_id);
let request = self.http.request(reqwest::Method::GET, &path)?;
let response = request.send().await?.error_for_status()?;
Ok(response.json::<JobDetails>().await?)
}
pub async fn status(&self, submission_id: &str) -> crate::Result<JobStatus> {
let details = self.get(submission_id).await?;
Ok(details.status)
}
pub async fn delete(&self, submission_id: &str) -> crate::Result<JobDeleteResponse> {
let path = format!("/api/jobs/{}", submission_id);
let request = self.http.request(reqwest::Method::DELETE, &path)?;
let response = request.send().await?.error_for_status()?;
Ok(response.json::<JobDeleteResponse>().await?)
}
pub async fn stop(&self, submission_id: &str) -> crate::Result<JobStopResponse> {
let path = format!("/api/jobs/{}/stop", submission_id);
let request = self.http.request(reqwest::Method::POST, &path)?;
let response = request.send().await?.error_for_status()?;
Ok(response.json::<JobStopResponse>().await?)
}
pub async fn logs(&self, submission_id: &str) -> crate::Result<JobLogsResponse> {
let path = format!("/api/jobs/{}/logs", submission_id);
let request = self.http.request(reqwest::Method::GET, &path)?;
let response = request.send().await?.error_for_status()?;
Ok(response.json::<JobLogsResponse>().await?)
}
pub async fn wait_for_terminal(
&self,
submission_id: &str,
max_duration: Option<Duration>,
) -> crate::Result<()> {
let start = Instant::now();
loop {
let status = self.status(submission_id).await?;
if status.is_terminal() {
return Ok(());
}
if let Some(max_duration) = max_duration
&& start.elapsed() >= max_duration
{
return Err(crate::Error::Generic(format!(
"Job {} did not reach terminal state within {:?}",
submission_id, max_duration
)));
}
Delay::new(Duration::from_millis(500)).await;
}
}
}