use std::borrow::Cow;
use std::collections::BTreeMap;
use std::io::Write;
use chrono::DateTime;
use chrono::Utc;
use cloud_copy::Alphanumeric;
use diesel::FromSqlRow;
use diesel::deserialize;
use diesel::expression::AsExpression;
use diesel::pg::Pg;
use diesel::pg::PgValue;
use diesel::prelude::*;
use diesel::serialize;
use diesel::sql_types;
use serde::Deserialize;
use serde::Serialize;
use serde::Serializer;
use tes::v1::types::requests::Task as RequestTask;
use tes::v1::types::responses::ExecutorLog;
use tes::v1::types::responses::MinimalTask as TesMinimalTask;
use tes::v1::types::responses::OutputFile;
use tes::v1::types::responses::Task as ResponseTask;
use tes::v1::types::task::Executor;
use tes::v1::types::task::Input;
use tes::v1::types::task::Output;
use tes::v1::types::task::Resources;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, diesel_derive_enum::DbEnum)]
#[db_enum(
existing_type_path = "crate::postgres::schema::sql_types::TaskState",
value_style = "SCREAMING_SNAKE_CASE"
)]
pub enum TaskState {
Unknown,
Queued,
Initializing,
Running,
Paused,
Complete,
ExecutorError,
SystemError,
Canceling,
Canceled,
Preempted,
}
impl From<TaskState> for tes::v1::types::task::State {
fn from(s: TaskState) -> Self {
use TaskState::*;
match s {
Unknown => Self::Unknown,
Queued => Self::Queued,
Initializing => Self::Initializing,
Running => Self::Running,
Paused => Self::Paused,
Complete => Self::Complete,
ExecutorError => Self::ExecutorError,
SystemError => Self::SystemError,
Canceling => Self::Canceling,
Canceled => Self::Canceled,
Preempted => Self::Preempted,
}
}
}
impl From<tes::v1::types::task::State> for TaskState {
fn from(s: tes::v1::types::task::State) -> Self {
use tes::v1::types::task::State::*;
match s {
Unknown => Self::Unknown,
Queued => Self::Queued,
Initializing => Self::Initializing,
Running => Self::Running,
Paused => Self::Paused,
Complete => Self::Complete,
ExecutorError => Self::ExecutorError,
SystemError => Self::SystemError,
Canceling => Self::Canceling,
Canceled => Self::Canceled,
Preempted => Self::Preempted,
}
}
}
#[derive(Debug, FromSqlRow, AsExpression)]
#[diesel(sql_type = diesel::sql_types::Jsonb)]
pub struct Json<T>(pub T);
impl<T> Json<T> {
pub fn into_inner(self) -> T {
self.0
}
}
impl<T: Serialize + std::fmt::Debug> serialize::ToSql<sql_types::Jsonb, Pg> for Json<T> {
fn to_sql<'b>(&self, out: &mut serialize::Output<'_, '_, Pg>) -> serialize::Result {
out.write_all(&[1])?;
serde_json::to_writer(out, &self.0)
.map(|_| serialize::IsNull::No)
.map_err(Into::into)
}
}
impl<T: for<'a> Deserialize<'a>> deserialize::FromSql<sql_types::Jsonb, Pg> for Json<T> {
fn from_sql(value: PgValue<'_>) -> deserialize::Result<Self> {
let bytes = value.as_bytes();
if bytes[0] != 1 {
return Err("unsupported JSONB encoding version".into());
}
Ok(Self(
serde_json::from_slice(&bytes[1..]).map_err(|e| format!("invalid JSON: {e}"))?,
))
}
}
#[derive(Debug)]
pub struct TagFilter {
key: String,
value: String,
}
impl TagFilter {
pub fn new(key: String, value: String) -> Self {
Self { key, value }
}
}
impl Serialize for TagFilter {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry(&self.key, &self.value)?;
map.end()
}
}
#[derive(Insertable)]
#[diesel(table_name = super::schema::tasks)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct NewTask<'a> {
pub state: TaskState,
pub username: &'a str,
pub tes_id: String,
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub inputs: Option<Json<Cow<'a, [Input]>>>,
pub outputs: Option<Json<Cow<'a, [Output]>>>,
pub cpu_cores: Option<i32>,
pub preemptible: Option<bool>,
pub ram_gb: Option<f64>,
pub disk_gb: Option<f64>,
pub zones: Option<&'a [String]>,
pub backend_parameters: Option<Json<Cow<'a, BTreeMap<String, serde_json::Value>>>>,
pub backend_parameters_strict: Option<bool>,
pub executors: Json<Cow<'a, [Executor]>>,
pub volumes: Option<&'a [String]>,
pub tags: Option<Json<Cow<'a, BTreeMap<String, String>>>>,
}
impl<'a> NewTask<'a> {
pub fn new(username: &'a str, task: &'a RequestTask) -> Self {
let resources = task.resources.as_ref();
Self {
state: TaskState::Unknown,
username,
tes_id: format!("{:#}", Alphanumeric::new(20)),
name: task.name.as_deref(),
description: task.description.as_deref(),
inputs: task.inputs.as_deref().map(|i| Json(i.into())),
outputs: task.outputs.as_deref().map(|o| Json(o.into())),
cpu_cores: resources.and_then(|r| r.cpu_cores),
preemptible: resources.and_then(|r| r.preemptible),
disk_gb: resources.and_then(|r| r.disk_gb),
ram_gb: resources.and_then(|r| r.ram_gb),
zones: resources.and_then(|r| r.zones.as_deref()),
backend_parameters: resources.and_then(|r| {
r.backend_parameters
.as_ref()
.map(|p| Json(Cow::Borrowed(p)))
}),
backend_parameters_strict: resources.and_then(|r| r.backend_parameters_strict),
executors: Json(task.executors.as_slice().into()),
volumes: task.volumes.as_deref(),
tags: task.tags.as_ref().map(|t| Json(Cow::Borrowed(t))),
}
}
}
#[derive(Debug, Queryable, Selectable, Identifiable)]
#[diesel(table_name = super::schema::tasks)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct MinimalTask {
pub id: i32,
pub tes_id: String,
pub state: TaskState,
}
impl From<MinimalTask> for TesMinimalTask {
fn from(task: MinimalTask) -> Self {
Self {
id: task.tes_id,
state: Some(task.state.into()),
}
}
}
#[derive(Queryable, Selectable, Identifiable, Associations, Debug, PartialEq)]
#[diesel(belongs_to(BasicTask, foreign_key = task_id))]
#[diesel(belongs_to(FullTask, foreign_key = task_id))]
#[diesel(table_name = super::schema::task_container_usage)]
#[diesel(primary_key(task_id, container_name))]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct ContainerUsage {
pub task_id: i32,
pub container_name: String,
pub peak_memory_bytes: Option<i64>,
pub memory_total_bytes: Option<i64>,
pub memory_sample_count: Option<i64>,
pub cpu_seconds: Option<f64>,
}
#[derive(Debug, Queryable, Selectable, Identifiable)]
#[diesel(table_name = super::schema::tasks)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct BasicTask {
pub id: i32,
pub tes_id: String,
pub state: TaskState,
pub name: Option<String>,
pub description: Option<String>,
pub inputs: Option<Json<Vec<Input>>>,
pub outputs: Option<Json<Vec<Output>>>,
pub cpu_cores: Option<i32>,
pub preemptible: Option<bool>,
pub ram_gb: Option<f64>,
pub disk_gb: Option<f64>,
pub zones: Option<Vec<Option<String>>>,
pub backend_parameters: Option<Json<BTreeMap<String, serde_json::Value>>>,
pub backend_parameters_strict: Option<bool>,
pub executors: Json<Vec<Executor>>,
pub volumes: Option<Vec<Option<String>>>,
pub tags: Option<Json<BTreeMap<String, String>>>,
pub output_files: Option<Json<Vec<OutputFile>>>,
pub creation_time: DateTime<Utc>,
}
impl BasicTask {
fn has_resources(&self) -> bool {
self.cpu_cores.is_some()
|| self.preemptible.is_some()
|| self.ram_gb.is_some()
|| self.disk_gb.is_some()
|| self.zones.is_some()
|| self.backend_parameters.is_some()
|| self.backend_parameters_strict.is_some()
}
}
impl From<BasicTask> for (ResponseTask, Vec<OutputFile>, Vec<String>) {
fn from(task: BasicTask) -> Self {
let resources = if task.has_resources() {
Some(Resources {
cpu_cores: task.cpu_cores,
preemptible: task.preemptible,
ram_gb: task.ram_gb,
disk_gb: task.disk_gb,
zones: task
.zones
.map(|z| z.into_iter().map(Option::unwrap_or_default).collect()),
backend_parameters: task.backend_parameters.map(Json::into_inner),
backend_parameters_strict: task.backend_parameters_strict,
})
} else {
None
};
let mut inputs = task.inputs.map(Json::into_inner);
if let Some(inputs) = inputs.as_mut() {
for input in inputs {
input.content = None;
}
}
(
ResponseTask {
id: Some(task.tes_id),
state: Some(task.state.into()),
name: task.name,
description: task.description,
inputs,
outputs: task.outputs.map(Json::into_inner),
resources,
executors: task.executors.into_inner(),
volumes: task
.volumes
.map(|z| z.into_iter().map(Option::unwrap_or_default).collect()),
tags: task.tags.map(Json::into_inner),
logs: None,
creation_time: Some(task.creation_time),
},
task.output_files.map(Json::into_inner).unwrap_or_default(),
Default::default(),
)
}
}
#[derive(Debug, Queryable, Selectable, Identifiable)]
#[diesel(table_name = super::schema::tasks)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct FullTask {
pub id: i32,
pub tes_id: String,
pub state: TaskState,
pub name: Option<String>,
pub description: Option<String>,
pub inputs: Option<Json<Vec<Input>>>,
pub outputs: Option<Json<Vec<Output>>>,
pub cpu_cores: Option<i32>,
pub preemptible: Option<bool>,
pub ram_gb: Option<f64>,
pub disk_gb: Option<f64>,
pub zones: Option<Vec<Option<String>>>,
pub backend_parameters: Option<Json<BTreeMap<String, serde_json::Value>>>,
pub backend_parameters_strict: Option<bool>,
pub executors: Json<Vec<Executor>>,
pub volumes: Option<Vec<Option<String>>>,
pub tags: Option<Json<BTreeMap<String, String>>>,
pub output_files: Option<Json<Vec<OutputFile>>>,
pub system_logs: Option<Vec<Option<String>>>,
pub creation_time: DateTime<Utc>,
}
impl FullTask {
fn has_resources(&self) -> bool {
self.cpu_cores.is_some()
|| self.preemptible.is_some()
|| self.ram_gb.is_some()
|| self.disk_gb.is_some()
|| self.zones.is_some()
|| self.backend_parameters.is_some()
|| self.backend_parameters_strict.is_some()
}
}
impl From<FullTask> for (ResponseTask, Vec<OutputFile>, Vec<String>) {
fn from(task: FullTask) -> Self {
let resources = if task.has_resources() {
Some(Resources {
cpu_cores: task.cpu_cores,
preemptible: task.preemptible,
ram_gb: task.ram_gb,
disk_gb: task.disk_gb,
zones: task
.zones
.map(|z| z.into_iter().map(Option::unwrap_or_default).collect()),
backend_parameters: task.backend_parameters.map(Json::into_inner),
backend_parameters_strict: task.backend_parameters_strict,
})
} else {
None
};
(
ResponseTask {
id: Some(task.tes_id),
state: Some(task.state.into()),
name: task.name,
description: task.description,
inputs: task.inputs.map(Json::into_inner),
outputs: task.outputs.map(Json::into_inner),
resources,
executors: task.executors.into_inner(),
volumes: task
.volumes
.map(|z| z.into_iter().map(Option::unwrap_or_default).collect()),
tags: task.tags.map(Json::into_inner),
logs: None,
creation_time: Some(task.creation_time),
},
task.output_files.map(Json::into_inner).unwrap_or_default(),
task.system_logs
.map(|l| l.into_iter().map(Option::unwrap_or_default).collect())
.unwrap_or_default(),
)
}
}
#[derive(Debug, Queryable, Selectable, Identifiable)]
#[diesel(table_name = super::schema::tasks)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct TaskTemplateData {
pub id: i32,
pub username: String,
pub tes_id: String,
pub inputs: Option<Json<Vec<Input>>>,
pub outputs: Option<Json<Vec<Output>>>,
pub cpu_cores: Option<i32>,
pub preemptible: Option<bool>,
pub ram_gb: Option<f64>,
pub disk_gb: Option<f64>,
pub executors: Json<Vec<Executor>>,
pub volumes: Option<Vec<Option<String>>>,
}
impl From<TaskTemplateData> for crate::TaskTemplateData {
fn from(data: TaskTemplateData) -> Self {
Self {
id: data.tes_id,
username: data.username,
preemptible: data.preemptible.unwrap_or(false),
cpu: data.cpu_cores,
memory: data.ram_gb,
disk: data.disk_gb,
inputs: data.inputs.map(Json::into_inner).unwrap_or_default(),
outputs: data.outputs.map(Json::into_inner).unwrap_or_default(),
volumes: data
.volumes
.map(|c| c.into_iter().map(Option::unwrap_or_default).collect())
.unwrap_or_default(),
executors: data.executors.into_inner(),
}
}
}
#[derive(Insertable)]
#[diesel(table_name = super::schema::containers)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct NewContainer<'a> {
pub task_id: i32,
pub name: &'a str,
pub executor_index: Option<i32>,
pub start_time: DateTime<Utc>,
pub end_time: DateTime<Utc>,
pub stdout: Option<Cow<'a, str>>,
pub stderr: Option<Cow<'a, str>>,
pub exit_code: i32,
}
impl<'a> NewContainer<'a> {
pub fn new(task_id: i32, container: crate::TerminatedContainer<'a>) -> Self {
Self {
task_id,
name: container.name,
executor_index: container.executor_index,
start_time: container.start_time,
end_time: container.end_time,
stdout: container.stdout,
stderr: container.stderr,
exit_code: container.exit_code,
}
}
}
#[derive(Queryable, Selectable, Identifiable, Associations, Debug, PartialEq)]
#[diesel(belongs_to(BasicTask, foreign_key = task_id))]
#[diesel(table_name = super::schema::containers)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct BasicContainer {
pub id: i32,
pub task_id: i32,
pub start_time: DateTime<Utc>,
pub end_time: DateTime<Utc>,
pub exit_code: i32,
}
impl From<BasicContainer> for ExecutorLog {
fn from(container: BasicContainer) -> Self {
Self {
start_time: Some(container.start_time),
end_time: Some(container.end_time),
stdout: None,
stderr: None,
exit_code: container.exit_code,
}
}
}
#[derive(Queryable, Selectable, Identifiable, Associations, Debug, PartialEq)]
#[diesel(belongs_to(FullTask, foreign_key = task_id))]
#[diesel(table_name = super::schema::containers)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct FullContainer {
pub id: i32,
pub task_id: i32,
pub start_time: DateTime<Utc>,
pub end_time: DateTime<Utc>,
pub stdout: Option<String>,
pub stderr: Option<String>,
pub exit_code: i32,
}
impl From<FullContainer> for ExecutorLog {
fn from(container: FullContainer) -> Self {
Self {
start_time: Some(container.start_time),
end_time: Some(container.end_time),
stdout: container.stdout,
stderr: container.stderr,
exit_code: container.exit_code,
}
}
}
#[derive(Insertable)]
#[diesel(table_name = super::schema::errors)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct NewError<'a> {
pub source: &'a str,
pub task_id: Option<i32>,
pub message: &'a str,
}