mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-03 19:28:22 +00:00
Add PrettyDuration, job timeout, job max schedule drift and job retry limit
This commit is contained in:
@@ -152,9 +152,13 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"max_age": {
|
"max_age": {
|
||||||
"description": "Maximum age of snapshots.\n\nFormat: 5m, 1h, 2d, ...\n\nAfter the specified time new snapshots will be created, and the old ones discarded.",
|
"description": "Maximum age of snapshots.\n\nFormat: 5m, 1h, 2d, ...\n\nAfter the specified time new snapshots will be created, and the old ones discarded.",
|
||||||
"type": [
|
"anyOf": [
|
||||||
"string",
|
{
|
||||||
"null"
|
"$ref": "#/definitions/PrettyDuration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"requests": {
|
"requests": {
|
||||||
@@ -295,6 +299,17 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"max_schedule_drift": {
|
||||||
|
"description": "Don't start job if past the due time by this amount, instead opting to wait for the next instance of it to be triggered.",
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/PrettyDuration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"description": "The package that contains the command to run. Defaults to the app config's package.",
|
"description": "The package that contains the command to run. Defaults to the app config's package.",
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
@@ -306,6 +321,24 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"retries": {
|
||||||
|
"type": [
|
||||||
|
"integer",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"timeout": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/PrettyDuration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"volumes": {
|
"volumes": {
|
||||||
"type": [
|
"type": [
|
||||||
"array",
|
"array",
|
||||||
@@ -397,9 +430,13 @@
|
|||||||
},
|
},
|
||||||
"timeout": {
|
"timeout": {
|
||||||
"description": "Request timeout.\n\nFormat: 1s, 5m, 11h, ...",
|
"description": "Request timeout.\n\nFormat: 1s, 5m, 11h, ...",
|
||||||
"type": [
|
"anyOf": [
|
||||||
"string",
|
{
|
||||||
"null"
|
"$ref": "#/definitions/PrettyDuration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"unhealthy_threshold": {
|
"unhealthy_threshold": {
|
||||||
@@ -492,9 +529,13 @@
|
|||||||
},
|
},
|
||||||
"timeout": {
|
"timeout": {
|
||||||
"description": "Request timeout.\n\nFormat: 1s, 5m, 11h, ...",
|
"description": "Request timeout.\n\nFormat: 1s, 5m, 11h, ...",
|
||||||
"type": [
|
"anyOf": [
|
||||||
"string",
|
{
|
||||||
"null"
|
"$ref": "#/definitions/PrettyDuration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -593,6 +634,9 @@
|
|||||||
"PackageSource": {
|
"PackageSource": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"PrettyDuration": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"Redirect": {
|
"Redirect": {
|
||||||
"description": "App redirect configuration.",
|
"description": "App redirect configuration.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use super::pretty_duration::PrettyDuration;
|
||||||
|
|
||||||
/// Defines an HTTP request.
|
/// Defines an HTTP request.
|
||||||
#[derive(
|
#[derive(
|
||||||
schemars::JsonSchema, serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone, Debug,
|
schemars::JsonSchema, serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone, Debug,
|
||||||
@@ -24,7 +26,7 @@ pub struct HttpRequest {
|
|||||||
///
|
///
|
||||||
/// Format: 1s, 5m, 11h, ...
|
/// Format: 1s, 5m, 11h, ...
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub timeout: Option<String>,
|
pub timeout: Option<PrettyDuration>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub expect: Option<HttpRequestExpect>,
|
pub expect: Option<HttpRequestExpect>,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use serde::{de::Error, Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::package::PackageSource;
|
use crate::package::PackageSource;
|
||||||
|
|
||||||
use super::{AppConfigCapabilityMemoryV1, AppVolume, HttpRequest};
|
use super::{pretty_duration::PrettyDuration, AppConfigCapabilityMemoryV1, AppVolume, HttpRequest};
|
||||||
|
|
||||||
/// Job configuration.
|
/// Job configuration.
|
||||||
#[derive(
|
#[derive(
|
||||||
@@ -69,6 +69,18 @@ pub struct ExecutableJob {
|
|||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub volumes: Option<Vec<AppVolume>>,
|
pub volumes: Option<Vec<AppVolume>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub timeout: Option<PrettyDuration>,
|
||||||
|
|
||||||
|
/// Don't start job if past the due time by this amount,
|
||||||
|
/// instead opting to wait for the next instance of it
|
||||||
|
/// to be triggered.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub max_schedule_drift: Option<PrettyDuration>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub retries: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
@@ -215,6 +227,9 @@ mod tests {
|
|||||||
name: "vol".to_owned(),
|
name: "vol".to_owned(),
|
||||||
mount: "/path/to/volume".to_owned(),
|
mount: "/path/to/volume".to_owned(),
|
||||||
}]),
|
}]),
|
||||||
|
timeout: Some("1m".parse().unwrap()),
|
||||||
|
max_schedule_drift: Some("2h".parse().unwrap()),
|
||||||
|
retries: None,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -234,7 +249,9 @@ execute:
|
|||||||
limit: '1000.0 MB'
|
limit: '1000.0 MB'
|
||||||
volumes:
|
volumes:
|
||||||
- name: vol
|
- name: vol
|
||||||
mount: /path/to/volume"#;
|
mount: /path/to/volume
|
||||||
|
timeout: '1m'
|
||||||
|
max_schedule_drift: '2h'"#;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serialized.trim(),
|
serialized.trim(),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
mod healthcheck;
|
mod healthcheck;
|
||||||
mod http;
|
mod http;
|
||||||
mod job;
|
mod job;
|
||||||
|
mod pretty_duration;
|
||||||
|
|
||||||
pub use self::{healthcheck::*, http::*, job::*};
|
pub use self::{healthcheck::*, http::*, job::*};
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use anyhow::{bail, Context};
|
use anyhow::{bail, Context};
|
||||||
use bytesize::ByteSize;
|
use bytesize::ByteSize;
|
||||||
|
use pretty_duration::PrettyDuration;
|
||||||
|
|
||||||
use crate::package::PackageSource;
|
use crate::package::PackageSource;
|
||||||
|
|
||||||
@@ -256,7 +258,7 @@ pub struct AppConfigCapabilityInstaBootV1 {
|
|||||||
/// After the specified time new snapshots will be created, and the old
|
/// After the specified time new snapshots will be created, and the old
|
||||||
/// ones discarded.
|
/// ones discarded.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub max_age: Option<String>,
|
pub max_age: Option<PrettyDuration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// App redirect configuration.
|
/// App redirect configuration.
|
||||||
|
|||||||
195
lib/config/src/app/pretty_duration.rs
Normal file
195
lib/config/src/app/pretty_duration.rs
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
str::FromStr,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{de::Error, Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct PrettyDuration {
|
||||||
|
unit: DurationUnit,
|
||||||
|
amount: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum DurationUnit {
|
||||||
|
Seconds,
|
||||||
|
Minutes,
|
||||||
|
Hours,
|
||||||
|
Days,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrettyDuration {
|
||||||
|
pub fn as_duration(&self) -> Duration {
|
||||||
|
match self.unit {
|
||||||
|
DurationUnit::Seconds => Duration::from_secs(self.amount),
|
||||||
|
DurationUnit::Minutes => Duration::from_secs(self.amount * 60),
|
||||||
|
DurationUnit::Hours => Duration::from_secs(self.amount * 60 * 60),
|
||||||
|
DurationUnit::Days => Duration::from_secs(self.amount * 60 * 60 * 24),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PrettyDuration {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
unit: DurationUnit::Seconds,
|
||||||
|
amount: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for PrettyDuration {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for PrettyDuration {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.as_duration().cmp(&other.as_duration())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsonSchema for PrettyDuration {
|
||||||
|
fn schema_name() -> String {
|
||||||
|
"PrettyDuration".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||||
|
String::json_schema(gen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for DurationUnit {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
DurationUnit::Seconds => write!(f, "s"),
|
||||||
|
DurationUnit::Minutes => write!(f, "m"),
|
||||||
|
DurationUnit::Hours => write!(f, "h"),
|
||||||
|
DurationUnit::Days => write!(f, "d"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for DurationUnit {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"s" | "S" => Ok(Self::Seconds),
|
||||||
|
"m" | "M" => Ok(Self::Minutes),
|
||||||
|
"h" | "H" => Ok(Self::Hours),
|
||||||
|
"d" | "D" => Ok(Self::Days),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for PrettyDuration {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}{}", self.amount, self.unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for PrettyDuration {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
<Self as Display>::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for PrettyDuration {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let (amount_str, unit_str) = s.split_at_checked(s.len() - 1).ok_or(())?;
|
||||||
|
Ok(Self {
|
||||||
|
unit: unit_str.parse()?,
|
||||||
|
amount: amount_str.parse().map_err(|_| ())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for PrettyDuration {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for PrettyDuration {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let repr: Cow<'de, str> = Cow::deserialize(deserializer)?;
|
||||||
|
repr.parse()
|
||||||
|
.map_err(|()| D::Error::custom("Failed to parse value as a duration"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn pretty_duration_serialize() {
|
||||||
|
assert_eq!(
|
||||||
|
PrettyDuration {
|
||||||
|
unit: DurationUnit::Seconds,
|
||||||
|
amount: 1234
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
"1234s"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
PrettyDuration {
|
||||||
|
unit: DurationUnit::Minutes,
|
||||||
|
amount: 345
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
"345m"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
PrettyDuration {
|
||||||
|
unit: DurationUnit::Hours,
|
||||||
|
amount: 56
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
"56h"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
PrettyDuration {
|
||||||
|
unit: DurationUnit::Days,
|
||||||
|
amount: 7
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
"7d"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn pretty_duration_deserialize() {
|
||||||
|
fn assert_deserializes_to(repr1: &str, repr2: &str, unit: DurationUnit, amount: u64) {
|
||||||
|
let duration = PrettyDuration { unit, amount };
|
||||||
|
assert_eq!(duration, repr1.parse().unwrap());
|
||||||
|
assert_eq!(duration, repr2.parse().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_deserializes_to("12s", "12S", DurationUnit::Seconds, 12);
|
||||||
|
assert_deserializes_to("34m", "34M", DurationUnit::Minutes, 34);
|
||||||
|
assert_deserializes_to("56h", "56H", DurationUnit::Hours, 56);
|
||||||
|
assert_deserializes_to("7d", "7D", DurationUnit::Days, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
pub fn cant_parse_nagative_duration() {
|
||||||
|
_ = "-12s".parse::<PrettyDuration>().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user